Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45d4984fde | |||
| ef2f605635 | |||
| 4532db552e | |||
| b9d438006c | |||
| 1eb96fa9e9 | |||
| c2694996e5 | |||
| 4d60133504 | |||
| c0ef300040 | |||
| b05d4fa7d3 | |||
| 84cd91bbbe | |||
| 718c258a07 |
@@ -58,7 +58,7 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.0.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.1.0
|
uses: docker/setup-buildx-action@v3.0.0
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.0.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.1.0
|
uses: docker/setup-buildx-action@v3.0.0
|
||||||
# Workaround to fix error:
|
# Workaround to fix error:
|
||||||
# failed to push: failed to copy: io: read/write on closed pipe
|
# failed to push: failed to copy: io: read/write on closed pipe
|
||||||
# See https://github.com/docker/build-push-action/issues/761
|
# See https://github.com/docker/build-push-action/issues/761
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
server-e2e-api:
|
server-e2e-api:
|
||||||
name: Server (e2e-api)
|
name: Server (e2e-api)
|
||||||
runs-on: ubuntu-latest
|
runs-on: mich
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
server-e2e-jobs:
|
server-e2e-jobs:
|
||||||
name: Server (e2e-jobs)
|
name: Server (e2e-jobs)
|
||||||
runs-on: ubuntu-latest
|
runs-on: mich
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|||||||
Generated
+404
-414
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,12 @@ services:
|
|||||||
- /dev/dri:/dev/dri
|
- /dev/dri:/dev/dri
|
||||||
- /dev/dma_heap:/dev/dma_heap
|
- /dev/dma_heap:/dev/dma_heap
|
||||||
- /dev/mpp_service:/dev/mpp_service
|
- /dev/mpp_service:/dev/mpp_service
|
||||||
|
volumes:
|
||||||
|
- /usr/bin/ffmpeg:/usr/bin/ffmpeg_mpp:ro
|
||||||
|
- /lib/aarch64-linux-gnu:/lib/ffmpeg-mpp:ro
|
||||||
|
- /lib/aarch64-linux-gnu/libblas.so.3:/lib/ffmpeg-mpp/libblas.so.3:ro # symlink is resolved by mounting
|
||||||
|
- /lib/aarch64-linux-gnu/liblapack.so.3:/lib/ffmpeg-mpp/liblapack.so.3:ro # symlink is resolved by mounting
|
||||||
|
- /lib/aarch64-linux-gnu/pulseaudio/libpulsecommon-15.99.so:/lib/ffmpeg-mpp/libpulsecommon-15.99.so:ro
|
||||||
|
|
||||||
vaapi:
|
vaapi:
|
||||||
devices:
|
devices:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ name: immich-e2e
|
|||||||
|
|
||||||
x-server-build: &server-common
|
x-server-build: &server-common
|
||||||
image: immich-server:latest
|
image: immich-server:latest
|
||||||
|
container_name: immich-e2e-server
|
||||||
build:
|
build:
|
||||||
context: ../
|
context: ../
|
||||||
dockerfile: server/Dockerfile
|
dockerfile: server/Dockerfile
|
||||||
@@ -22,16 +23,14 @@ x-server-build: &server-common
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
immich-server:
|
immich-server:
|
||||||
container_name: immich-e2e-server
|
|
||||||
command: [ "./start.sh", "immich" ]
|
command: [ "./start.sh", "immich" ]
|
||||||
<<: *server-common
|
<<: *server-common
|
||||||
ports:
|
ports:
|
||||||
- 2283:3001
|
- 2283:3001
|
||||||
|
|
||||||
immich-microservices:
|
# immich-microservices:
|
||||||
container_name: immich-e2e-microservices
|
# command: [ "./start.sh", "microservices" ]
|
||||||
command: [ "./start.sh", "microservices" ]
|
# <<: *server-common
|
||||||
<<: *server-common
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5
|
image: redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5
|
||||||
|
|||||||
Generated
-104
@@ -12,14 +12,11 @@
|
|||||||
"@immich/cli": "file:../cli",
|
"@immich/cli": "file:../cli",
|
||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@playwright/test": "^1.41.2",
|
"@playwright/test": "^1.41.2",
|
||||||
"@types/luxon": "^3.4.2",
|
|
||||||
"@types/node": "^20.11.17",
|
"@types/node": "^20.11.17",
|
||||||
"@types/pg": "^8.11.0",
|
"@types/pg": "^8.11.0",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"@vitest/coverage-v8": "^1.3.0",
|
"@vitest/coverage-v8": "^1.3.0",
|
||||||
"luxon": "^3.4.4",
|
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"socket.io-client": "^4.7.4",
|
|
||||||
"supertest": "^6.3.4",
|
"supertest": "^6.3.4",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vitest": "^1.3.0"
|
"vitest": "^1.3.0"
|
||||||
@@ -784,12 +781,6 @@
|
|||||||
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
|
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@socket.io/component-emitter": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/cookiejar": {
|
"node_modules/@types/cookiejar": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
|
||||||
@@ -808,12 +799,6 @@
|
|||||||
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
|
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/luxon": {
|
|
||||||
"version": "3.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
|
|
||||||
"integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/methods": {
|
"node_modules/@types/methods": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||||
@@ -1278,28 +1263,6 @@
|
|||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io-client": {
|
|
||||||
"version": "6.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
|
|
||||||
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
|
||||||
"debug": "~4.3.1",
|
|
||||||
"engine.io-parser": "~5.2.1",
|
|
||||||
"ws": "~8.11.0",
|
|
||||||
"xmlhttprequest-ssl": "~2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/engine.io-parser": {
|
|
||||||
"version": "5.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
|
|
||||||
"integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
@@ -1741,15 +1704,6 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/luxon": {
|
|
||||||
"version": "3.4.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
|
||||||
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.7",
|
"version": "0.30.7",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
|
||||||
@@ -2392,34 +2346,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socket.io-client": {
|
|
||||||
"version": "4.7.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz",
|
|
||||||
"integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
|
||||||
"debug": "~4.3.2",
|
|
||||||
"engine.io-client": "~6.5.2",
|
|
||||||
"socket.io-parser": "~4.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/socket.io-parser": {
|
|
||||||
"version": "4.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
|
||||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
|
||||||
"debug": "~4.3.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
@@ -2817,36 +2743,6 @@
|
|||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
|
||||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": "^5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xmlhttprequest-ssl": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
@@ -16,14 +16,11 @@
|
|||||||
"@immich/cli": "file:../cli",
|
"@immich/cli": "file:../cli",
|
||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@playwright/test": "^1.41.2",
|
"@playwright/test": "^1.41.2",
|
||||||
"@types/luxon": "^3.4.2",
|
|
||||||
"@types/node": "^20.11.17",
|
"@types/node": "^20.11.17",
|
||||||
"@types/pg": "^8.11.0",
|
"@types/pg": "^8.11.0",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"@vitest/coverage-v8": "^1.3.0",
|
"@vitest/coverage-v8": "^1.3.0",
|
||||||
"luxon": "^3.4.4",
|
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"socket.io-client": "^4.7.4",
|
|
||||||
"supertest": "^6.3.4",
|
"supertest": "^6.3.4",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vitest": "^1.3.0"
|
"vitest": "^1.3.0"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ActivityCreateDto,
|
ActivityCreateDto,
|
||||||
AlbumResponseDto,
|
AlbumResponseDto,
|
||||||
AssetFileUploadResponseDto,
|
AssetResponseDto,
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
ReactionType,
|
ReactionType,
|
||||||
createActivity as create,
|
createActivity as create,
|
||||||
@@ -16,13 +16,13 @@ import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|||||||
describe('/activity', () => {
|
describe('/activity', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let nonOwner: LoginResponseDto;
|
let nonOwner: LoginResponseDto;
|
||||||
let asset: AssetFileUploadResponseDto;
|
let asset: AssetResponseDto;
|
||||||
let album: AlbumResponseDto;
|
let album: AlbumResponseDto;
|
||||||
|
|
||||||
const createActivity = (dto: ActivityCreateDto, accessToken?: string) =>
|
const createActivity = (dto: ActivityCreateDto, accessToken?: string) =>
|
||||||
create(
|
create(
|
||||||
{ activityCreateDto: dto },
|
{ activityCreateDto: dto },
|
||||||
{ headers: asBearerAuth(accessToken || admin.accessToken) },
|
{ headers: asBearerAuth(accessToken || admin.accessToken) }
|
||||||
);
|
);
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -40,7 +40,7 @@ describe('/activity', () => {
|
|||||||
sharedWithUserIds: [nonOwner.userId],
|
sharedWithUserIds: [nonOwner.userId],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ headers: asBearerAuth(admin.accessToken) },
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ describe('/activity', () => {
|
|||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID'])),
|
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID']))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ describe('/activity', () => {
|
|||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID'])),
|
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID']))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ describe('/activity', () => {
|
|||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(expect.arrayContaining(['assetId must be a UUID'])),
|
errorDto.badRequest(expect.arrayContaining(['assetId must be a UUID']))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ describe('/activity', () => {
|
|||||||
assetIds: [asset.id],
|
assetIds: [asset.id],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ headers: asBearerAuth(admin.accessToken) },
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
);
|
);
|
||||||
|
|
||||||
const [reaction] = await Promise.all([
|
const [reaction] = await Promise.all([
|
||||||
@@ -216,7 +216,7 @@ describe('/activity', () => {
|
|||||||
.send({ albumId: uuidDto.invalid });
|
.send({ albumId: uuidDto.invalid });
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID'])),
|
errorDto.badRequest(expect.arrayContaining(['albumId must be a UUID']))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ describe('/activity', () => {
|
|||||||
errorDto.badRequest([
|
errorDto.badRequest([
|
||||||
'comment must be a string',
|
'comment must be a string',
|
||||||
'comment should not be empty',
|
'comment should not be empty',
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ describe('/activity', () => {
|
|||||||
describe('DELETE /activity/:id', () => {
|
describe('DELETE /activity/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(app).delete(
|
const { status, body } = await request(app).delete(
|
||||||
`/activity/${uuidDto.notFound}`,
|
`/activity/${uuidDto.notFound}`
|
||||||
);
|
);
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
@@ -421,7 +421,7 @@ describe('/activity', () => {
|
|||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest('Not found or no activity.delete access'),
|
errorDto.badRequest('Not found or no activity.delete access')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -432,7 +432,7 @@ describe('/activity', () => {
|
|||||||
type: ReactionType.Comment,
|
type: ReactionType.Comment,
|
||||||
comment: 'This is a test comment',
|
comment: 'This is a test comment',
|
||||||
},
|
},
|
||||||
nonOwner.accessToken,
|
nonOwner.accessToken
|
||||||
);
|
);
|
||||||
|
|
||||||
const { status } = await request(app)
|
const { status } = await request(app)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AlbumResponseDto,
|
AlbumResponseDto,
|
||||||
AssetFileUploadResponseDto,
|
AssetResponseDto,
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
SharedLinkType,
|
SharedLinkType,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
@@ -21,8 +21,8 @@ const user2NotShared = 'user2NotShared';
|
|||||||
describe('/album', () => {
|
describe('/album', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let user1: LoginResponseDto;
|
let user1: LoginResponseDto;
|
||||||
let user1Asset1: AssetFileUploadResponseDto;
|
let user1Asset1: AssetResponseDto;
|
||||||
let user1Asset2: AssetFileUploadResponseDto;
|
let user1Asset2: AssetResponseDto;
|
||||||
let user1Albums: AlbumResponseDto[];
|
let user1Albums: AlbumResponseDto[];
|
||||||
let user2: LoginResponseDto;
|
let user2: LoginResponseDto;
|
||||||
let user2Albums: AlbumResponseDto[];
|
let user2Albums: AlbumResponseDto[];
|
||||||
@@ -95,7 +95,7 @@ describe('/album', () => {
|
|||||||
|
|
||||||
await deleteUser(
|
await deleteUser(
|
||||||
{ id: user3.userId },
|
{ id: user3.userId },
|
||||||
{ headers: asBearerAuth(admin.accessToken) },
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ describe('/album', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toEqual(400);
|
expect(status).toEqual(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(['shared must be a boolean value']),
|
errorDto.badRequest(['shared must be a boolean value'])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ describe('/album', () => {
|
|||||||
albumName: user2SharedUser,
|
albumName: user2SharedUser,
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ describe('/album', () => {
|
|||||||
albumName: user1NotShared,
|
albumName: user1NotShared,
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ describe('/album', () => {
|
|||||||
albumName: user2SharedUser,
|
albumName: user2SharedUser,
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ describe('/album', () => {
|
|||||||
albumName: user1NotShared,
|
albumName: user1NotShared,
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ describe('/album', () => {
|
|||||||
describe('GET /album/:id', () => {
|
describe('GET /album/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(app).get(
|
const { status, body } = await request(app).get(
|
||||||
`/album/${user1Albums[0].id}`,
|
`/album/${user1Albums[0].id}`
|
||||||
);
|
);
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
@@ -361,7 +361,7 @@ describe('/album', () => {
|
|||||||
describe('PUT /album/:id/assets', () => {
|
describe('PUT /album/:id/assets', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(app).put(
|
const { status, body } = await request(app).put(
|
||||||
`/album/${user1Albums[0].id}/assets`,
|
`/album/${user1Albums[0].id}/assets`
|
||||||
);
|
);
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
@@ -519,7 +519,7 @@ describe('/album', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
sharedUsers: [expect.objectContaining({ id: user2.userId })],
|
sharedUsers: [expect.objectContaining({ id: user2.userId })],
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,481 +0,0 @@
|
|||||||
import {
|
|
||||||
AssetFileUploadResponseDto,
|
|
||||||
AssetResponseDto,
|
|
||||||
LoginResponseDto,
|
|
||||||
SharedLinkType,
|
|
||||||
} from '@immich/sdk';
|
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import { Socket } from 'socket.io-client';
|
|
||||||
import { createUserDto, uuidDto } from 'src/fixtures';
|
|
||||||
import { errorDto } from 'src/responses';
|
|
||||||
import { apiUtils, app, dbUtils } from 'src/utils';
|
|
||||||
import request from 'supertest';
|
|
||||||
import { beforeAll, describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
const today = DateTime.fromObject({
|
|
||||||
year: 2023,
|
|
||||||
month: 11,
|
|
||||||
day: 3,
|
|
||||||
}) as DateTime<true>;
|
|
||||||
const yesterday = today.minus({ days: 1 });
|
|
||||||
|
|
||||||
describe('/asset', () => {
|
|
||||||
let admin: LoginResponseDto;
|
|
||||||
let user1: LoginResponseDto;
|
|
||||||
let user2: LoginResponseDto;
|
|
||||||
let userStats: LoginResponseDto;
|
|
||||||
let asset1: AssetFileUploadResponseDto;
|
|
||||||
let asset2: AssetFileUploadResponseDto;
|
|
||||||
let asset3: AssetFileUploadResponseDto;
|
|
||||||
let asset4: AssetFileUploadResponseDto; // user2 asset
|
|
||||||
let asset5: AssetFileUploadResponseDto;
|
|
||||||
let asset6: AssetFileUploadResponseDto;
|
|
||||||
let ws: Socket;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
apiUtils.setup();
|
|
||||||
await dbUtils.reset();
|
|
||||||
admin = await apiUtils.adminSetup({ onboarding: false });
|
|
||||||
[user1, user2, userStats] = await Promise.all([
|
|
||||||
apiUtils.userSetup(admin.accessToken, createUserDto.user1),
|
|
||||||
apiUtils.userSetup(admin.accessToken, createUserDto.user2),
|
|
||||||
apiUtils.userSetup(admin.accessToken, createUserDto.user3),
|
|
||||||
]);
|
|
||||||
|
|
||||||
[asset1, asset2, asset3, asset4, asset5, asset6] = await Promise.all([
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
apiUtils.createAsset(
|
|
||||||
user1.accessToken,
|
|
||||||
{
|
|
||||||
isFavorite: true,
|
|
||||||
isExternal: true,
|
|
||||||
isReadOnly: true,
|
|
||||||
fileCreatedAt: yesterday.toISO(),
|
|
||||||
fileModifiedAt: yesterday.toISO(),
|
|
||||||
},
|
|
||||||
{ filename: 'example.mp4' },
|
|
||||||
),
|
|
||||||
apiUtils.createAsset(user2.accessToken),
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
|
|
||||||
// stats
|
|
||||||
apiUtils.createAsset(userStats.accessToken),
|
|
||||||
apiUtils.createAsset(userStats.accessToken, { isFavorite: true }),
|
|
||||||
apiUtils.createAsset(userStats.accessToken, { isArchived: true }),
|
|
||||||
apiUtils.createAsset(
|
|
||||||
userStats.accessToken,
|
|
||||||
{
|
|
||||||
isArchived: true,
|
|
||||||
isFavorite: true,
|
|
||||||
},
|
|
||||||
{ filename: 'example.mp4' },
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const person1 = await apiUtils.createPerson(user1.accessToken, {
|
|
||||||
name: 'Test Person',
|
|
||||||
});
|
|
||||||
await dbUtils.createFace({ assetId: asset1.id, personId: person1.id });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /asset/:id', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).get(
|
|
||||||
`/asset/${uuidDto.notFound}`,
|
|
||||||
);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
expect(status).toBe(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require a valid id', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get(`/asset/${uuidDto.invalid}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require access', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get(`/asset/${asset4.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.noPermission);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get the asset info', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get(`/asset/${asset1.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toMatchObject({ id: asset1.id });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with a shared link', async () => {
|
|
||||||
const sharedLink = await apiUtils.createSharedLink(user1.accessToken, {
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
assetIds: [asset1.id],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { status, body } = await request(app).get(
|
|
||||||
`/asset/${asset1.id}?key=${sharedLink.key}`,
|
|
||||||
);
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toMatchObject({ id: asset1.id });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not send people data for shared links for un-authenticated users', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get(`/asset/${asset1.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toEqual(200);
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
id: asset1.id,
|
|
||||||
isFavorite: false,
|
|
||||||
people: [
|
|
||||||
{
|
|
||||||
birthDate: null,
|
|
||||||
id: expect.any(String),
|
|
||||||
isHidden: false,
|
|
||||||
name: 'Test Person',
|
|
||||||
thumbnailPath: '/my/awesome/thumbnail.jpg',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const sharedLink = await apiUtils.createSharedLink(user1.accessToken, {
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
assetIds: [asset1.id],
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await request(app).get(
|
|
||||||
`/asset/${asset1.id}?key=${sharedLink.key}`,
|
|
||||||
);
|
|
||||||
expect(data.status).toBe(200);
|
|
||||||
expect(data.body).toMatchObject({ people: [] });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /asset/statistics', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).get('/asset/statistics');
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return stats of all assets', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/asset/statistics')
|
|
||||||
.set('Authorization', `Bearer ${userStats.accessToken}`);
|
|
||||||
|
|
||||||
expect(body).toEqual({ images: 3, videos: 1, total: 4 });
|
|
||||||
expect(status).toBe(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return stats of all favored assets', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/asset/statistics')
|
|
||||||
.set('Authorization', `Bearer ${userStats.accessToken}`)
|
|
||||||
.query({ isFavorite: true });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({ images: 1, videos: 1, total: 2 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return stats of all archived assets', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/asset/statistics')
|
|
||||||
.set('Authorization', `Bearer ${userStats.accessToken}`)
|
|
||||||
.query({ isArchived: true });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({ images: 1, videos: 1, total: 2 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return stats of all favored and archived assets', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/asset/statistics')
|
|
||||||
.set('Authorization', `Bearer ${userStats.accessToken}`)
|
|
||||||
.query({ isFavorite: true, isArchived: true });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({ images: 0, videos: 1, total: 1 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return stats of all assets neither favored nor archived', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/asset/statistics')
|
|
||||||
.set('Authorization', `Bearer ${userStats.accessToken}`)
|
|
||||||
.query({ isFavorite: false, isArchived: false });
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({ images: 1, videos: 0, total: 1 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /asset/random', () => {
|
|
||||||
beforeAll(async () => {
|
|
||||||
await Promise.all([
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
apiUtils.createAsset(user1.accessToken),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).get('/asset/random');
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(Array(10))('should return 1 random assets', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/asset/random')
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
|
|
||||||
const assets: AssetResponseDto[] = body;
|
|
||||||
expect(assets.length).toBe(1);
|
|
||||||
expect(assets[0].ownerId).toBe(user1.userId);
|
|
||||||
//
|
|
||||||
// assets owned by user2
|
|
||||||
expect(assets[0].id).not.toBe(asset4.id);
|
|
||||||
// assets owned by user1
|
|
||||||
expect([asset1.id, asset2.id, asset3.id]).toContain(assets[0].id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(Array(10))('should return 2 random assets', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/asset/random?count=2')
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
|
|
||||||
const assets: AssetResponseDto[] = body;
|
|
||||||
expect(assets.length).toBe(2);
|
|
||||||
|
|
||||||
for (const asset of assets) {
|
|
||||||
expect(asset.ownerId).toBe(user1.userId);
|
|
||||||
// assets owned by user1
|
|
||||||
expect([asset1.id, asset2.id, asset3.id]).toContain(asset.id);
|
|
||||||
// assets owned by user2
|
|
||||||
expect(asset.id).not.toBe(asset4.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(Array(10))(
|
|
||||||
'should return 1 asset if there are 10 assets in the database but user 2 only has 1',
|
|
||||||
async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.get('/[]asset/random')
|
|
||||||
.set('Authorization', `Bearer ${user2.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual([expect.objectContaining({ id: asset4.id })]);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
it('should return error', async () => {
|
|
||||||
const { status } = await request(app)
|
|
||||||
.get('/asset/random?count=ABC')
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PUT /asset/:id', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).put(
|
|
||||||
`/asset/:${uuidDto.notFound}`,
|
|
||||||
);
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require a valid id', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${uuidDto.invalid}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.badRequest(['id must be a UUID']));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require access', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${asset4.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.noPermission);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should favorite an asset', async () => {
|
|
||||||
const before = await apiUtils.getAssetInfo(user1.accessToken, asset1.id);
|
|
||||||
expect(before.isFavorite).toBe(false);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${asset1.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ isFavorite: true });
|
|
||||||
expect(body).toMatchObject({ id: asset1.id, isFavorite: true });
|
|
||||||
expect(status).toEqual(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should archive an asset', async () => {
|
|
||||||
const before = await apiUtils.getAssetInfo(user1.accessToken, asset1.id);
|
|
||||||
expect(before.isArchived).toBe(false);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${asset1.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ isArchived: true });
|
|
||||||
expect(body).toMatchObject({ id: asset1.id, isArchived: true });
|
|
||||||
expect(status).toEqual(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update date time original', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${asset1.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ dateTimeOriginal: '2023-11-19T18:11:00.000-07:00' });
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
id: asset1.id,
|
|
||||||
exifInfo: expect.objectContaining({
|
|
||||||
dateTimeOriginal: '2023-11-20T01:11:00.000Z',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
expect(status).toEqual(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject invalid gps coordinates', async () => {
|
|
||||||
for (const test of [
|
|
||||||
{ latitude: 12 },
|
|
||||||
{ longitude: 12 },
|
|
||||||
{ latitude: 12, longitude: 'abc' },
|
|
||||||
{ latitude: 'abc', longitude: 12 },
|
|
||||||
{ latitude: null, longitude: 12 },
|
|
||||||
{ latitude: 12, longitude: null },
|
|
||||||
{ latitude: 91, longitude: 12 },
|
|
||||||
{ latitude: -91, longitude: 12 },
|
|
||||||
{ latitude: 12, longitude: -181 },
|
|
||||||
{ latitude: 12, longitude: 181 },
|
|
||||||
]) {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${asset1.id}`)
|
|
||||||
.send(test)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update gps data', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${asset1.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ latitude: 12, longitude: 12 });
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
id: asset1.id,
|
|
||||||
exifInfo: expect.objectContaining({ latitude: 12, longitude: 12 }),
|
|
||||||
});
|
|
||||||
expect(status).toEqual(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set the description', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${asset1.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ description: 'Test asset description' });
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
id: asset1.id,
|
|
||||||
exifInfo: expect.objectContaining({
|
|
||||||
description: 'Test asset description',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
expect(status).toEqual(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return tagged people', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.put(`/asset/${asset1.id}`)
|
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
|
||||||
.send({ isFavorite: true });
|
|
||||||
expect(status).toEqual(200);
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
id: asset1.id,
|
|
||||||
isFavorite: true,
|
|
||||||
people: [
|
|
||||||
{
|
|
||||||
birthDate: null,
|
|
||||||
id: expect.any(String),
|
|
||||||
isHidden: false,
|
|
||||||
name: 'Test Person',
|
|
||||||
thumbnailPath: '/my/awesome/thumbnail.jpg',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DELETE /asset', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.delete(`/asset`)
|
|
||||||
.send({ ids: [uuidDto.notFound] });
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require a valid uuid', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.delete(`/asset`)
|
|
||||||
.send({ ids: [uuidDto.invalid] })
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(
|
|
||||||
errorDto.badRequest(['each value in ids must be a UUID']),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when the id is not found', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.delete(`/asset`)
|
|
||||||
.send({ ids: [uuidDto.notFound] })
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(
|
|
||||||
errorDto.badRequest('Not found or no asset.delete access'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should move an asset to the trash', async () => {
|
|
||||||
const { id: assetId } = await apiUtils.createAsset(admin.accessToken);
|
|
||||||
|
|
||||||
const before = await apiUtils.getAssetInfo(admin.accessToken, assetId);
|
|
||||||
expect(before.isTrashed).toBe(false);
|
|
||||||
|
|
||||||
const { status } = await request(app)
|
|
||||||
.delete('/asset')
|
|
||||||
.send({ ids: [assetId] })
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
expect(status).toBe(204);
|
|
||||||
|
|
||||||
const after = await apiUtils.getAssetInfo(admin.accessToken, assetId);
|
|
||||||
expect(after.isTrashed).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AssetFileUploadResponseDto, LoginResponseDto } from '@immich/sdk';
|
import { AssetResponseDto, LoginResponseDto } from '@immich/sdk';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
import { apiUtils, app, dbUtils } from 'src/utils';
|
import { apiUtils, app, dbUtils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
@@ -6,7 +6,7 @@ import { beforeAll, describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
describe('/download', () => {
|
describe('/download', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let asset1: AssetFileUploadResponseDto;
|
let asset1: AssetResponseDto;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
apiUtils.setup();
|
apiUtils.setup();
|
||||||
@@ -35,7 +35,7 @@ describe('/download', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
archives: [expect.objectContaining({ assetIds: [asset1.id] })],
|
archives: [expect.objectContaining({ assetIds: [asset1.id] })],
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -43,7 +43,7 @@ describe('/download', () => {
|
|||||||
describe('POST /download/asset/:id', () => {
|
describe('POST /download/asset/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(app).post(
|
const { status, body } = await request(app).post(
|
||||||
`/download/asset/${asset1.id}`,
|
`/download/asset/${asset1.id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
AlbumResponseDto,
|
AlbumResponseDto,
|
||||||
AssetFileUploadResponseDto,
|
AssetResponseDto,
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
|
SharedLinkCreateDto,
|
||||||
SharedLinkResponseDto,
|
SharedLinkResponseDto,
|
||||||
SharedLinkType,
|
SharedLinkType,
|
||||||
|
createSharedLink as create,
|
||||||
createAlbum,
|
createAlbum,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
@@ -15,8 +17,8 @@ import { beforeAll, describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
describe('/shared-link', () => {
|
describe('/shared-link', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let asset1: AssetFileUploadResponseDto;
|
let asset1: AssetResponseDto;
|
||||||
let asset2: AssetFileUploadResponseDto;
|
let asset2: AssetResponseDto;
|
||||||
let user1: LoginResponseDto;
|
let user1: LoginResponseDto;
|
||||||
let user2: LoginResponseDto;
|
let user2: LoginResponseDto;
|
||||||
let album: AlbumResponseDto;
|
let album: AlbumResponseDto;
|
||||||
@@ -48,11 +50,11 @@ describe('/shared-link', () => {
|
|||||||
[album, deletedAlbum, metadataAlbum] = await Promise.all([
|
[album, deletedAlbum, metadataAlbum] = await Promise.all([
|
||||||
createAlbum(
|
createAlbum(
|
||||||
{ createAlbumDto: { albumName: 'album' } },
|
{ createAlbumDto: { albumName: 'album' } },
|
||||||
{ headers: asBearerAuth(user1.accessToken) },
|
{ headers: asBearerAuth(user1.accessToken) }
|
||||||
),
|
),
|
||||||
createAlbum(
|
createAlbum(
|
||||||
{ createAlbumDto: { albumName: 'deleted album' } },
|
{ createAlbumDto: { albumName: 'deleted album' } },
|
||||||
{ headers: asBearerAuth(user2.accessToken) },
|
{ headers: asBearerAuth(user2.accessToken) }
|
||||||
),
|
),
|
||||||
createAlbum(
|
createAlbum(
|
||||||
{
|
{
|
||||||
@@ -61,7 +63,7 @@ describe('/shared-link', () => {
|
|||||||
assetIds: [asset1.id],
|
assetIds: [asset1.id],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ headers: asBearerAuth(user1.accessToken) },
|
{ headers: asBearerAuth(user1.accessToken) }
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -104,7 +106,7 @@ describe('/shared-link', () => {
|
|||||||
|
|
||||||
await deleteUser(
|
await deleteUser(
|
||||||
{ id: user2.userId },
|
{ id: user2.userId },
|
||||||
{ headers: asBearerAuth(admin.accessToken) },
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,7 +132,7 @@ describe('/shared-link', () => {
|
|||||||
expect.objectContaining({ id: linkWithPassword.id }),
|
expect.objectContaining({ id: linkWithPassword.id }),
|
||||||
expect.objectContaining({ id: linkWithMetadata.id }),
|
expect.objectContaining({ id: linkWithMetadata.id }),
|
||||||
expect.objectContaining({ id: linkWithoutMetadata.id }),
|
expect.objectContaining({ id: linkWithoutMetadata.id }),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,7 +166,7 @@ describe('/shared-link', () => {
|
|||||||
album,
|
album,
|
||||||
userId: user1.userId,
|
userId: user1.userId,
|
||||||
type: SharedLinkType.Album,
|
type: SharedLinkType.Album,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -206,7 +208,7 @@ describe('/shared-link', () => {
|
|||||||
album,
|
album,
|
||||||
userId: user1.userId,
|
userId: user1.userId,
|
||||||
type: SharedLinkType.Album,
|
type: SharedLinkType.Album,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -223,7 +225,7 @@ describe('/shared-link', () => {
|
|||||||
localDateTime: expect.any(String),
|
localDateTime: expect.any(String),
|
||||||
fileCreatedAt: expect.any(String),
|
fileCreatedAt: expect.any(String),
|
||||||
exifInfo: expect.any(Object),
|
exifInfo: expect.any(Object),
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
expect(body.album).toBeDefined();
|
expect(body.album).toBeDefined();
|
||||||
});
|
});
|
||||||
@@ -248,7 +250,7 @@ describe('/shared-link', () => {
|
|||||||
describe('GET /shared-link/:id', () => {
|
describe('GET /shared-link/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(app).get(
|
const { status, body } = await request(app).get(
|
||||||
`/shared-link/${linkWithAlbum.id}`,
|
`/shared-link/${linkWithAlbum.id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
@@ -266,7 +268,7 @@ describe('/shared-link', () => {
|
|||||||
album,
|
album,
|
||||||
userId: user1.userId,
|
userId: user1.userId,
|
||||||
type: SharedLinkType.Album,
|
type: SharedLinkType.Album,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -277,7 +279,7 @@ describe('/shared-link', () => {
|
|||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({ message: 'Shared link not found' }),
|
expect.objectContaining({ message: 'Shared link not found' })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -309,7 +311,7 @@ describe('/shared-link', () => {
|
|||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({ message: 'Invalid albumId' }),
|
expect.objectContaining({ message: 'Invalid albumId' })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -321,7 +323,7 @@ describe('/shared-link', () => {
|
|||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({ message: 'Invalid assetIds' }),
|
expect.objectContaining({ message: 'Invalid assetIds' })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -336,7 +338,7 @@ describe('/shared-link', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: SharedLinkType.Album,
|
type: SharedLinkType.Album,
|
||||||
userId: user1.userId,
|
userId: user1.userId,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -373,7 +375,7 @@ describe('/shared-link', () => {
|
|||||||
type: SharedLinkType.Album,
|
type: SharedLinkType.Album,
|
||||||
userId: user1.userId,
|
userId: user1.userId,
|
||||||
description: 'foo',
|
description: 'foo',
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -425,7 +427,7 @@ describe('/shared-link', () => {
|
|||||||
describe('DELETE /shared-link/:id', () => {
|
describe('DELETE /shared-link/:id', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(app).delete(
|
const { status, body } = await request(app).delete(
|
||||||
`/shared-link/${linkWithAlbum.id}`,
|
`/shared-link/${linkWithAlbum.id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(status).toBe(401);
|
expect(status).toBe(401);
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
import { LoginResponseDto, getAllAssets } from '@immich/sdk';
|
|
||||||
import { Socket } from 'socket.io-client';
|
|
||||||
import { errorDto } from 'src/responses';
|
|
||||||
import { apiUtils, app, asBearerAuth, dbUtils, wsUtils } from 'src/utils';
|
|
||||||
import request from 'supertest';
|
|
||||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
describe('/trash', () => {
|
|
||||||
let admin: LoginResponseDto;
|
|
||||||
let ws: Socket;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
apiUtils.setup();
|
|
||||||
await dbUtils.reset();
|
|
||||||
admin = await apiUtils.adminSetup({ onboarding: false });
|
|
||||||
ws = await wsUtils.connect(admin.accessToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
wsUtils.disconnect(ws);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /trash/empty', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).post('/trash/empty');
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should empty the trash', async () => {
|
|
||||||
const { id: assetId } = await apiUtils.createAsset(admin.accessToken);
|
|
||||||
await apiUtils.deleteAssets(admin.accessToken, [assetId]);
|
|
||||||
|
|
||||||
const before = await getAllAssets(
|
|
||||||
{},
|
|
||||||
{ headers: asBearerAuth(admin.accessToken) },
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(before.length).toBeGreaterThanOrEqual(1);
|
|
||||||
|
|
||||||
const { status } = await request(app)
|
|
||||||
.post('/trash/empty')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
expect(status).toBe(204);
|
|
||||||
|
|
||||||
await wsUtils.once(ws, 'on_asset_delete');
|
|
||||||
|
|
||||||
const after = await getAllAssets(
|
|
||||||
{},
|
|
||||||
{ headers: asBearerAuth(admin.accessToken) },
|
|
||||||
);
|
|
||||||
expect(after.length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /trash/restore', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).post('/trash/restore');
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should restore all trashed assets', async () => {
|
|
||||||
const { id: assetId } = await apiUtils.createAsset(admin.accessToken);
|
|
||||||
await apiUtils.deleteAssets(admin.accessToken, [assetId]);
|
|
||||||
|
|
||||||
const before = await apiUtils.getAssetInfo(admin.accessToken, assetId);
|
|
||||||
expect(before.isTrashed).toBe(true);
|
|
||||||
|
|
||||||
const { status } = await request(app)
|
|
||||||
.post('/trash/restore')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
expect(status).toBe(204);
|
|
||||||
|
|
||||||
const after = await apiUtils.getAssetInfo(admin.accessToken, assetId);
|
|
||||||
expect(after.isTrashed).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /trash/restore/assets', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).post('/trash/restore/assets');
|
|
||||||
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should restore a trashed asset by id', async () => {
|
|
||||||
const { id: assetId } = await apiUtils.createAsset(admin.accessToken);
|
|
||||||
await apiUtils.deleteAssets(admin.accessToken, [assetId]);
|
|
||||||
|
|
||||||
const before = await apiUtils.getAssetInfo(admin.accessToken, assetId);
|
|
||||||
expect(before.isTrashed).toBe(true);
|
|
||||||
|
|
||||||
const { status } = await request(app)
|
|
||||||
.post('/trash/restore/assets')
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
|
||||||
.send({ ids: [assetId] });
|
|
||||||
expect(status).toBe(204);
|
|
||||||
|
|
||||||
const after = await apiUtils.getAssetInfo(admin.accessToken, assetId);
|
|
||||||
expect(after.isTrashed).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import { apiUtils, cliUtils, dbUtils, immichCli } from 'src/utils';
|
import { apiUtils, cliUtils, dbUtils, immichCli } from 'src/utils';
|
||||||
import { beforeAll, describe, expect, it } from 'vitest';
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
describe(`immich server-info`, () => {
|
describe(`immich server-info`, () => {
|
||||||
beforeAll(async () => {
|
beforeAll(() => {
|
||||||
apiUtils.setup();
|
apiUtils.setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
await dbUtils.reset();
|
await dbUtils.reset();
|
||||||
await cliUtils.login();
|
await cliUtils.login();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { getAllAlbums, getAllAssets } from '@immich/sdk';
|
import { getAllAlbums, getAllAssets } from '@immich/sdk';
|
||||||
import { mkdir, readdir, rm, symlink } from 'fs/promises';
|
|
||||||
import {
|
import {
|
||||||
apiUtils,
|
apiUtils,
|
||||||
asKeyAuth,
|
asKeyAuth,
|
||||||
@@ -9,18 +8,18 @@ import {
|
|||||||
testAssetDir,
|
testAssetDir,
|
||||||
} from 'src/utils';
|
} from 'src/utils';
|
||||||
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
import { mkdir, readdir, rm, symlink } from 'fs/promises';
|
||||||
|
|
||||||
describe(`immich upload`, () => {
|
describe(`immich upload`, () => {
|
||||||
let key: string;
|
let key: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(() => {
|
||||||
apiUtils.setup();
|
apiUtils.setup();
|
||||||
await dbUtils.reset();
|
|
||||||
key = await cliUtils.login();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await dbUtils.reset(['assets', 'albums']);
|
await dbUtils.reset();
|
||||||
|
key = await cliUtils.login();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('immich upload --recursive', () => {
|
describe('immich upload --recursive', () => {
|
||||||
@@ -34,7 +33,7 @@ describe(`immich upload`, () => {
|
|||||||
expect(stdout.split('\n')).toEqual(
|
expect(stdout.split('\n')).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.stringContaining('Successfully uploaded 9 assets'),
|
expect.stringContaining('Successfully uploaded 9 assets'),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ describe(`immich upload`, () => {
|
|||||||
expect.stringContaining('Successfully uploaded 9 assets'),
|
expect.stringContaining('Successfully uploaded 9 assets'),
|
||||||
expect.stringContaining('Successfully created 1 new album'),
|
expect.stringContaining('Successfully created 1 new album'),
|
||||||
expect.stringContaining('Successfully updated 9 assets'),
|
expect.stringContaining('Successfully updated 9 assets'),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
expect(stderr).toBe('');
|
expect(stderr).toBe('');
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
@@ -78,7 +77,7 @@ describe(`immich upload`, () => {
|
|||||||
expect(response1.stdout.split('\n')).toEqual(
|
expect(response1.stdout.split('\n')).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.stringContaining('Successfully uploaded 9 assets'),
|
expect.stringContaining('Successfully uploaded 9 assets'),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
expect(response1.stderr).toBe('');
|
expect(response1.stderr).toBe('');
|
||||||
expect(response1.exitCode).toBe(0);
|
expect(response1.exitCode).toBe(0);
|
||||||
@@ -98,10 +97,10 @@ describe(`immich upload`, () => {
|
|||||||
expect(response2.stdout.split('\n')).toEqual(
|
expect(response2.stdout.split('\n')).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.stringContaining(
|
expect.stringContaining(
|
||||||
'All assets were already uploaded, nothing to do.',
|
'All assets were already uploaded, nothing to do.'
|
||||||
),
|
),
|
||||||
expect.stringContaining('Successfully updated 9 assets'),
|
expect.stringContaining('Successfully updated 9 assets'),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
expect(response2.stderr).toBe('');
|
expect(response2.stderr).toBe('');
|
||||||
expect(response2.exitCode).toBe(0);
|
expect(response2.exitCode).toBe(0);
|
||||||
@@ -128,7 +127,7 @@ describe(`immich upload`, () => {
|
|||||||
expect.stringContaining('Successfully uploaded 9 assets'),
|
expect.stringContaining('Successfully uploaded 9 assets'),
|
||||||
expect.stringContaining('Successfully created 1 new album'),
|
expect.stringContaining('Successfully created 1 new album'),
|
||||||
expect.stringContaining('Successfully updated 9 assets'),
|
expect.stringContaining('Successfully updated 9 assets'),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
expect(stderr).toBe('');
|
expect(stderr).toBe('');
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
@@ -149,7 +148,7 @@ describe(`immich upload`, () => {
|
|||||||
for (const file of filesToLink) {
|
for (const file of filesToLink) {
|
||||||
await symlink(
|
await symlink(
|
||||||
`${testAssetDir}/albums/nature/${file}`,
|
`${testAssetDir}/albums/nature/${file}`,
|
||||||
`/tmp/albums/nature/${file}`,
|
`/tmp/albums/nature/${file}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +166,7 @@ describe(`immich upload`, () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.stringContaining('Successfully uploaded 9 assets'),
|
expect.stringContaining('Successfully uploaded 9 assets'),
|
||||||
expect.stringContaining('Deleting assets that have been uploaded'),
|
expect.stringContaining('Deleting assets that have been uploaded'),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
expect(stderr).toBe('');
|
expect(stderr).toBe('');
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
|
|||||||
+27
-92
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
AssetFileUploadResponseDto,
|
AssetResponseDto,
|
||||||
CreateAlbumDto,
|
CreateAlbumDto,
|
||||||
CreateAssetDto,
|
CreateAssetDto,
|
||||||
CreateUserDto,
|
CreateUserDto,
|
||||||
@@ -11,8 +11,6 @@ import {
|
|||||||
createSharedLink,
|
createSharedLink,
|
||||||
createUser,
|
createUser,
|
||||||
defaults,
|
defaults,
|
||||||
deleteAssets,
|
|
||||||
getAssetInfo,
|
|
||||||
login,
|
login,
|
||||||
setAdminOnboarding,
|
setAdminOnboarding,
|
||||||
signUpAdmin,
|
signUpAdmin,
|
||||||
@@ -25,7 +23,6 @@ import { access } from 'node:fs/promises';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import pg from 'pg';
|
import pg from 'pg';
|
||||||
import { io, type Socket } from 'socket.io-client';
|
|
||||||
import { loginDto, signupDto } from 'src/fixtures';
|
import { loginDto, signupDto } from 'src/fixtures';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
@@ -42,19 +39,15 @@ const directoryExists = (directory: string) =>
|
|||||||
export const testAssetDir = path.resolve(`./../server/test/assets/`);
|
export const testAssetDir = path.resolve(`./../server/test/assets/`);
|
||||||
|
|
||||||
const serverContainerName = 'immich-e2e-server';
|
const serverContainerName = 'immich-e2e-server';
|
||||||
const mediaDir = '/usr/src/app/upload';
|
const uploadMediaDir = '/usr/src/app/upload/upload';
|
||||||
const dirs = [
|
|
||||||
`"${mediaDir}/thumbs"`,
|
|
||||||
`"${mediaDir}/upload"`,
|
|
||||||
`"${mediaDir}/library"`,
|
|
||||||
].join(' ');
|
|
||||||
|
|
||||||
if (!(await directoryExists(`${testAssetDir}/albums`))) {
|
if (!(await directoryExists(`${testAssetDir}/albums`))) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Test assets not found. Please checkout https://github.com/immich-app/test-assets into ${testAssetDir} before testing`,
|
`Test assets not found. Please checkout https://github.com/immich-app/test-assets into ${testAssetDir} before testing`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setBaseUrl = () => (defaults.baseUrl = app);
|
||||||
export const asBearerAuth = (accessToken: string) => ({
|
export const asBearerAuth = (accessToken: string) => ({
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
});
|
});
|
||||||
@@ -66,7 +59,7 @@ let client: pg.Client | null = null;
|
|||||||
export const fileUtils = {
|
export const fileUtils = {
|
||||||
reset: async () => {
|
reset: async () => {
|
||||||
await execPromise(
|
await execPromise(
|
||||||
`docker exec -i "${serverContainerName}" /bin/bash -c "rm -rf ${dirs} && mkdir ${dirs}"`,
|
`docker exec -i "${serverContainerName}" rm -R "${uploadMediaDir}"`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -88,7 +81,7 @@ export const dbUtils = {
|
|||||||
|
|
||||||
await client.query(
|
await client.query(
|
||||||
'INSERT INTO asset_faces ("assetId", "personId", "embedding") VALUES ($1, $2, $3)',
|
'INSERT INTO asset_faces ("assetId", "personId", "embedding") VALUES ($1, $2, $3)',
|
||||||
[assetId, personId, embedding],
|
[assetId, personId, embedding]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
setPersonThumbnail: async (personId: string) => {
|
setPersonThumbnail: async (personId: string) => {
|
||||||
@@ -98,14 +91,14 @@ export const dbUtils = {
|
|||||||
|
|
||||||
await client.query(
|
await client.query(
|
||||||
`UPDATE "person" set "thumbnailPath" = '/my/awesome/thumbnail.jpg' where "id" = $1`,
|
`UPDATE "person" set "thumbnailPath" = '/my/awesome/thumbnail.jpg' where "id" = $1`,
|
||||||
[personId],
|
[personId]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
reset: async (tables?: string[]) => {
|
reset: async (tables?: string[]) => {
|
||||||
try {
|
try {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
client = new pg.Client(
|
client = new pg.Client(
|
||||||
'postgres://postgres:postgres@127.0.0.1:5433/immich',
|
'postgres://postgres:postgres@127.0.0.1:5433/immich'
|
||||||
);
|
);
|
||||||
await client.connect();
|
await client.connect();
|
||||||
}
|
}
|
||||||
@@ -177,42 +170,10 @@ export interface AdminSetupOptions {
|
|||||||
onboarding?: boolean;
|
onboarding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const wsUtils = {
|
|
||||||
connect: async (accessToken: string) => {
|
|
||||||
const websocket = io('http://127.0.0.1:2283', {
|
|
||||||
path: '/api/socket.io',
|
|
||||||
transports: ['websocket'],
|
|
||||||
extraHeaders: { Authorization: `Bearer ${accessToken}` },
|
|
||||||
autoConnect: false,
|
|
||||||
forceNew: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise<Socket>((resolve) => {
|
|
||||||
websocket.on('connect', () => resolve(websocket));
|
|
||||||
websocket.connect();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
disconnect: (ws: Socket) => {
|
|
||||||
if (ws?.connected) {
|
|
||||||
ws.disconnect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
once: <T = any>(ws: Socket, event: string): Promise<T> => {
|
|
||||||
return new Promise<T>((resolve, reject) => {
|
|
||||||
const timeout = setTimeout(() => reject(new Error('Timeout')), 4000);
|
|
||||||
ws.once(event, (data: T) => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
resolve(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const apiUtils = {
|
export const apiUtils = {
|
||||||
setup: () => {
|
setup: () => {
|
||||||
defaults.baseUrl = app;
|
setBaseUrl();
|
||||||
},
|
},
|
||||||
|
|
||||||
adminSetup: async (options?: AdminSetupOptions) => {
|
adminSetup: async (options?: AdminSetupOptions) => {
|
||||||
options = options || { onboarding: true };
|
options = options || { onboarding: true };
|
||||||
|
|
||||||
@@ -226,7 +187,7 @@ export const apiUtils = {
|
|||||||
userSetup: async (accessToken: string, dto: CreateUserDto) => {
|
userSetup: async (accessToken: string, dto: CreateUserDto) => {
|
||||||
await createUser(
|
await createUser(
|
||||||
{ createUserDto: dto },
|
{ createUserDto: dto },
|
||||||
{ headers: asBearerAuth(accessToken) },
|
{ headers: asBearerAuth(accessToken) }
|
||||||
);
|
);
|
||||||
return login({
|
return login({
|
||||||
loginCredentialDto: { email: dto.email, password: dto.password },
|
loginCredentialDto: { email: dto.email, password: dto.password },
|
||||||
@@ -235,74 +196,48 @@ export const apiUtils = {
|
|||||||
createApiKey: (accessToken: string) => {
|
createApiKey: (accessToken: string) => {
|
||||||
return createApiKey(
|
return createApiKey(
|
||||||
{ apiKeyCreateDto: { name: 'e2e' } },
|
{ apiKeyCreateDto: { name: 'e2e' } },
|
||||||
{ headers: asBearerAuth(accessToken) },
|
{ headers: asBearerAuth(accessToken) }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
createAlbum: (accessToken: string, dto: CreateAlbumDto) =>
|
createAlbum: (accessToken: string, dto: CreateAlbumDto) =>
|
||||||
createAlbum(
|
createAlbum(
|
||||||
{ createAlbumDto: dto },
|
{ createAlbumDto: dto },
|
||||||
{ headers: asBearerAuth(accessToken) },
|
{ headers: asBearerAuth(accessToken) }
|
||||||
),
|
),
|
||||||
createAsset: async (
|
createAsset: async (
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
dto?: Partial<Omit<CreateAssetDto, 'assetData'>>,
|
dto?: Omit<CreateAssetDto, 'assetData'>
|
||||||
data?: {
|
|
||||||
bytes?: Buffer;
|
|
||||||
filename?: string;
|
|
||||||
},
|
|
||||||
) => {
|
) => {
|
||||||
const _dto = {
|
dto = dto || {
|
||||||
deviceAssetId: 'test-1',
|
deviceAssetId: 'test-1',
|
||||||
deviceId: 'test',
|
deviceId: 'test',
|
||||||
fileCreatedAt: new Date().toISOString(),
|
fileCreatedAt: new Date().toISOString(),
|
||||||
fileModifiedAt: new Date().toISOString(),
|
fileModifiedAt: new Date().toISOString(),
|
||||||
...(dto || {}),
|
|
||||||
};
|
};
|
||||||
|
const { body } = await request(app)
|
||||||
const _assetData = {
|
|
||||||
bytes: randomBytes(32),
|
|
||||||
filename: 'example.jpg',
|
|
||||||
...(data || {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const builder = request(app)
|
|
||||||
.post(`/asset/upload`)
|
.post(`/asset/upload`)
|
||||||
.attach('assetData', _assetData.bytes, _assetData.filename)
|
.field('deviceAssetId', dto.deviceAssetId)
|
||||||
|
.field('deviceId', dto.deviceId)
|
||||||
|
.field('fileCreatedAt', dto.fileCreatedAt)
|
||||||
|
.field('fileModifiedAt', dto.fileModifiedAt)
|
||||||
|
.attach('assetData', randomBytes(32), 'example.jpg')
|
||||||
.set('Authorization', `Bearer ${accessToken}`);
|
.set('Authorization', `Bearer ${accessToken}`);
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(_dto)) {
|
return body as AssetResponseDto;
|
||||||
builder.field(key, String(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { body } = await builder;
|
|
||||||
|
|
||||||
return body as AssetFileUploadResponseDto;
|
|
||||||
},
|
},
|
||||||
getAssetInfo: (accessToken: string, id: string) =>
|
createPerson: async (accessToken: string, dto: PersonUpdateDto) => {
|
||||||
getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }),
|
|
||||||
deleteAssets: (accessToken: string, ids: string[]) =>
|
|
||||||
deleteAssets(
|
|
||||||
{ assetBulkDeleteDto: { ids } },
|
|
||||||
{ headers: asBearerAuth(accessToken) },
|
|
||||||
),
|
|
||||||
createPerson: async (accessToken: string, dto?: PersonUpdateDto) => {
|
|
||||||
// TODO fix createPerson to accept a body
|
// TODO fix createPerson to accept a body
|
||||||
let person = await createPerson({ headers: asBearerAuth(accessToken) });
|
const { id } = await createPerson({ headers: asBearerAuth(accessToken) });
|
||||||
await dbUtils.setPersonThumbnail(person.id);
|
await dbUtils.setPersonThumbnail(id);
|
||||||
|
|
||||||
if (!dto) {
|
|
||||||
return person;
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatePerson(
|
return updatePerson(
|
||||||
{ id: person.id, personUpdateDto: dto },
|
{ id, personUpdateDto: dto },
|
||||||
{ headers: asBearerAuth(accessToken) },
|
{ headers: asBearerAuth(accessToken) }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
createSharedLink: (accessToken: string, dto: SharedLinkCreateDto) =>
|
createSharedLink: (accessToken: string, dto: SharedLinkCreateDto) =>
|
||||||
createSharedLink(
|
createSharedLink(
|
||||||
{ sharedLinkCreateDto: dto },
|
{ sharedLinkCreateDto: dto },
|
||||||
{ headers: asBearerAuth(accessToken) },
|
{ headers: asBearerAuth(accessToken) }
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ test.describe('Shared Links', () => {
|
|||||||
let asset: AssetResponseDto;
|
let asset: AssetResponseDto;
|
||||||
let album: AlbumResponseDto;
|
let album: AlbumResponseDto;
|
||||||
let sharedLink: SharedLinkResponseDto;
|
let sharedLink: SharedLinkResponseDto;
|
||||||
let sharedLinkPassword: SharedLinkResponseDto;
|
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
test.beforeAll(async () => {
|
||||||
apiUtils.setup();
|
apiUtils.setup();
|
||||||
@@ -30,16 +29,17 @@ test.describe('Shared Links', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ headers: asBearerAuth(admin.accessToken) }
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
|
// { headers: asBearerAuth(admin.accessToken)},
|
||||||
|
);
|
||||||
|
sharedLink = await createSharedLink(
|
||||||
|
{
|
||||||
|
sharedLinkCreateDto: {
|
||||||
|
type: SharedLinkType.Album,
|
||||||
|
albumId: album.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ headers: asBearerAuth(admin.accessToken) }
|
||||||
);
|
);
|
||||||
sharedLink = await apiUtils.createSharedLink(admin.accessToken, {
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
albumId: album.id,
|
|
||||||
});
|
|
||||||
sharedLinkPassword = await apiUtils.createSharedLink(admin.accessToken, {
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
albumId: album.id,
|
|
||||||
password: 'test-password',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.afterAll(async () => {
|
test.afterAll(async () => {
|
||||||
@@ -55,16 +55,4 @@ test.describe('Shared Links', () => {
|
|||||||
await page.getByRole('button', { name: 'Download' }).click();
|
await page.getByRole('button', { name: 'Download' }).click();
|
||||||
await page.getByText('DOWNLOADING').waitFor();
|
await page.getByText('DOWNLOADING').waitFor();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('enter password for a shared link', async ({ page }) => {
|
|
||||||
await page.goto(`/share/${sharedLinkPassword.key}`);
|
|
||||||
await page.getByPlaceholder('Password').fill('test-password');
|
|
||||||
await page.getByRole('button', { name: 'Submit' }).click();
|
|
||||||
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('show error for invalid shared link', async ({ page }) => {
|
|
||||||
await page.goto('/share/invalid');
|
|
||||||
await page.getByRole('heading', { name: 'Invalid share key' }).waitFor();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -165,14 +165,6 @@ class InferenceModel(ABC):
|
|||||||
def providers_default(self) -> list[str]:
|
def providers_default(self) -> list[str]:
|
||||||
available_providers = set(ort.get_available_providers())
|
available_providers = set(ort.get_available_providers())
|
||||||
log.debug(f"Available ORT providers: {available_providers}")
|
log.debug(f"Available ORT providers: {available_providers}")
|
||||||
if (openvino := "OpenVINOExecutionProvider") in available_providers:
|
|
||||||
device_ids: list[str] = ort.capi._pybind_state.get_available_openvino_device_ids()
|
|
||||||
log.debug(f"Available OpenVINO devices: {device_ids}")
|
|
||||||
|
|
||||||
gpu_devices = [device_id for device_id in device_ids if device_id.startswith("GPU")]
|
|
||||||
if not gpu_devices:
|
|
||||||
log.warning("No GPU device found in OpenVINO. Falling back to CPU.")
|
|
||||||
available_providers.remove(openvino)
|
|
||||||
return [provider for provider in SUPPORTED_PROVIDERS if provider in available_providers]
|
return [provider for provider in SUPPORTED_PROVIDERS if provider in available_providers]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -192,7 +184,15 @@ class InferenceModel(ABC):
|
|||||||
case "CPUExecutionProvider" | "CUDAExecutionProvider":
|
case "CPUExecutionProvider" | "CUDAExecutionProvider":
|
||||||
option = {"arena_extend_strategy": "kSameAsRequested"}
|
option = {"arena_extend_strategy": "kSameAsRequested"}
|
||||||
case "OpenVINOExecutionProvider":
|
case "OpenVINOExecutionProvider":
|
||||||
option = {"device_type": "GPU_FP32"}
|
try:
|
||||||
|
device_ids: list[str] = ort.capi._pybind_state.get_available_openvino_device_ids()
|
||||||
|
log.debug(f"Available OpenVINO devices: {device_ids}")
|
||||||
|
gpu_devices = [device_id for device_id in device_ids if device_id.startswith("GPU")]
|
||||||
|
option = {"device_id": gpu_devices[0]} if gpu_devices else {}
|
||||||
|
except AttributeError as e:
|
||||||
|
log.warning("Failed to get OpenVINO device IDs. Using default options.")
|
||||||
|
log.error(e)
|
||||||
|
option = {}
|
||||||
case _:
|
case _:
|
||||||
option = {}
|
option = {}
|
||||||
options.append(option)
|
options.append(option)
|
||||||
|
|||||||
@@ -45,23 +45,11 @@ class TestBase:
|
|||||||
assert encoder.providers == self.CUDA_EP
|
assert encoder.providers == self.CUDA_EP
|
||||||
|
|
||||||
@pytest.mark.providers(OV_EP)
|
@pytest.mark.providers(OV_EP)
|
||||||
def test_sets_openvino_provider_if_available(self, providers: list[str], mocker: MockerFixture) -> None:
|
def test_sets_openvino_provider_if_available(self, providers: list[str]) -> None:
|
||||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
|
||||||
mocked.get_available_openvino_device_ids.return_value = ["GPU.0", "CPU"]
|
|
||||||
|
|
||||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||||
|
|
||||||
assert encoder.providers == self.OV_EP
|
assert encoder.providers == self.OV_EP
|
||||||
|
|
||||||
@pytest.mark.providers(OV_EP)
|
|
||||||
def test_avoids_openvino_if_gpu_not_available(self, providers: list[str], mocker: MockerFixture) -> None:
|
|
||||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
|
||||||
mocked.get_available_openvino_device_ids.return_value = ["CPU"]
|
|
||||||
|
|
||||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
|
||||||
|
|
||||||
assert encoder.providers == self.CPU_EP
|
|
||||||
|
|
||||||
@pytest.mark.providers(CUDA_EP_OUT_OF_ORDER)
|
@pytest.mark.providers(CUDA_EP_OUT_OF_ORDER)
|
||||||
def test_sets_providers_in_correct_order(self, providers: list[str]) -> None:
|
def test_sets_providers_in_correct_order(self, providers: list[str]) -> None:
|
||||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||||
@@ -80,14 +68,22 @@ class TestBase:
|
|||||||
|
|
||||||
assert encoder.providers == providers
|
assert encoder.providers == providers
|
||||||
|
|
||||||
def test_sets_default_provider_options(self, mocker: MockerFixture) -> None:
|
def test_sets_default_provider_options(self) -> None:
|
||||||
|
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"])
|
||||||
|
|
||||||
|
assert encoder.provider_options == [
|
||||||
|
{},
|
||||||
|
{"arena_extend_strategy": "kSameAsRequested"},
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_sets_openvino_device_id_if_possible(self, mocker: MockerFixture) -> None:
|
||||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
||||||
mocked.get_available_openvino_device_ids.return_value = ["GPU.0", "CPU"]
|
mocked.get_available_openvino_device_ids.return_value = ["GPU.0", "CPU"]
|
||||||
|
|
||||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"])
|
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"])
|
||||||
|
|
||||||
assert encoder.provider_options == [
|
assert encoder.provider_options == [
|
||||||
{"device_type": "GPU_FP32"},
|
{"device_id": "GPU.0"},
|
||||||
{"arena_extend_strategy": "kSameAsRequested"},
|
{"arena_extend_strategy": "kSameAsRequested"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mambaorg/micromamba:bookworm-slim@sha256:6038b89363c9181215f3d9e8ce2720c880e224537f4028a854482e43a9b4998a as builder
|
FROM mambaorg/micromamba:bookworm-slim@sha256:926cac38640709f90f3fef2a3f730733b5c350be612f0d14706be8833b79ad8c as builder
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
TRANSFORMERS_CACHE=/cache \
|
TRANSFORMERS_CACHE=/cache \
|
||||||
|
|||||||
Generated
+75
-75
@@ -1250,13 +1250,13 @@ test = ["Cython (>=0.29.24,<0.30.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpx"
|
name = "httpx"
|
||||||
version = "0.27.0"
|
version = "0.26.0"
|
||||||
description = "The next generation HTTP client."
|
description = "The next generation HTTP client."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
|
{file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"},
|
||||||
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
|
{file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2101,61 +2101,61 @@ numpy = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orjson"
|
name = "orjson"
|
||||||
version = "3.9.15"
|
version = "3.9.14"
|
||||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "orjson-3.9.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a"},
|
{file = "orjson-3.9.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:793f6c9448ab6eb7d4974b4dde3f230345c08ca6c7995330fbceeb43a5c8aa5e"},
|
||||||
{file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04"},
|
{file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bc7928d161840096adc956703494b5c0193ede887346f028216cac0af87500"},
|
||||||
{file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4"},
|
{file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58b36f54da759602d8e2f7dad958752d453dfe2c7122767bc7f765e17dc59959"},
|
||||||
{file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75"},
|
{file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:abcda41ecdc950399c05eff761c3de91485d9a70d8227cb599ad3a66afe93bcc"},
|
||||||
{file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab"},
|
{file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df76ecd17b1b3627bddfd689faaf206380a1a38cc9f6c4075bd884eaedcf46c2"},
|
||||||
{file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58"},
|
{file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d450a8e0656efb5d0fcb062157b918ab02dcca73278975b4ee9ea49e2fcf5bd5"},
|
||||||
{file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99"},
|
{file = "orjson-3.9.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95c03137b0cf66517c8baa65770507a756d3a89489d8ecf864ea92348e1beabe"},
|
||||||
{file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe"},
|
{file = "orjson-3.9.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20837e10835c98973673406d6798e10f821e7744520633811a5a3d809762d8cc"},
|
||||||
{file = "orjson-3.9.15-cp310-none-win32.whl", hash = "sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7"},
|
{file = "orjson-3.9.14-cp310-none-win32.whl", hash = "sha256:1f7b6f3ef10ae8e3558abb729873d033dbb5843507c66b1c0767e32502ba96bb"},
|
||||||
{file = "orjson-3.9.15-cp310-none-win_amd64.whl", hash = "sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb"},
|
{file = "orjson-3.9.14-cp310-none-win_amd64.whl", hash = "sha256:ea890e6dc1711aeec0a33b8520e395c2f3d59ead5b4351a788e06bf95fc7ba81"},
|
||||||
{file = "orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2"},
|
{file = "orjson-3.9.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c19009ff37f033c70acd04b636380379499dac2cba27ae7dfc24f304deabbc81"},
|
||||||
{file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f"},
|
{file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19cdea0664aec0b7f385be84986d4defd3334e9c3c799407686ee1c26f7b8251"},
|
||||||
{file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc"},
|
{file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:135d518f73787ce323b1a5e21fb854fe22258d7a8ae562b81a49d6c7f826f2a3"},
|
||||||
{file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1"},
|
{file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2cf1d0557c61c75e18cf7d69fb689b77896e95553e212c0cc64cf2087944b84"},
|
||||||
{file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5"},
|
{file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7c11667421df2d8b18b021223505dcc3ee51be518d54e4dc49161ac88ac2b87"},
|
||||||
{file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde"},
|
{file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eefc41ba42e75ed88bc396d8fe997beb20477f3e7efa000cd7a47eda452fbb2"},
|
||||||
{file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404"},
|
{file = "orjson-3.9.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:917311d6a64d1c327c0dfda1e41f3966a7fb72b11ca7aa2e7a68fcccc7db35d9"},
|
||||||
{file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357"},
|
{file = "orjson-3.9.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4dc1c132259b38d12c6587d190cd09cd76e3b5273ce71fe1372437b4cbc65f6f"},
|
||||||
{file = "orjson-3.9.15-cp311-none-win32.whl", hash = "sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7"},
|
{file = "orjson-3.9.14-cp311-none-win32.whl", hash = "sha256:6f39a10408478f4c05736a74da63727a1ae0e83e3533d07b19443400fe8591ca"},
|
||||||
{file = "orjson-3.9.15-cp311-none-win_amd64.whl", hash = "sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8"},
|
{file = "orjson-3.9.14-cp311-none-win_amd64.whl", hash = "sha256:26280a7fcb62d8257f634c16acebc3bec626454f9ab13558bbf7883b9140760e"},
|
||||||
{file = "orjson-3.9.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73"},
|
{file = "orjson-3.9.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:08e722a8d06b13b67a51f247a24938d1a94b4b3862e40e0eef3b2e98c99cd04c"},
|
||||||
{file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a"},
|
{file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2591faa0c031cf3f57e5bce1461cfbd6160f3f66b5a72609a130924917cb07d"},
|
||||||
{file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7"},
|
{file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2450d87dd7b4f277f4c5598faa8b49a0c197b91186c47a2c0b88e15531e4e3e"},
|
||||||
{file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e"},
|
{file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90903d2908158a2c9077a06f11e27545de610af690fb178fd3ba6b32492d4d1c"},
|
||||||
{file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d"},
|
{file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce6f095eef0026eae76fc212f20f786011ecf482fc7df2f4c272a8ae6dd7b1ef"},
|
||||||
{file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494"},
|
{file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:751250a31fef2bac05a2da2449aae7142075ea26139271f169af60456d8ad27a"},
|
||||||
{file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068"},
|
{file = "orjson-3.9.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a1af21160a38ee8be3f4fcf24ee4b99e6184cadc7f915d599f073f478a94d2c"},
|
||||||
{file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda"},
|
{file = "orjson-3.9.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:449bf090b2aa4e019371d7511a6ea8a5a248139205c27d1834bb4b1e3c44d936"},
|
||||||
{file = "orjson-3.9.15-cp312-none-win_amd64.whl", hash = "sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2"},
|
{file = "orjson-3.9.14-cp312-none-win_amd64.whl", hash = "sha256:a603161318ff699784943e71f53899983b7dee571b4dd07c336437c9c5a272b0"},
|
||||||
{file = "orjson-3.9.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25"},
|
{file = "orjson-3.9.14-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:814f288c011efdf8f115c5ebcc1ab94b11da64b207722917e0ceb42f52ef30a3"},
|
||||||
{file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1"},
|
{file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88cafb100af68af3b9b29b5ccd09fdf7a48c63327916c8c923a94c336d38dd3"},
|
||||||
{file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c"},
|
{file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba3518b999f88882ade6686f1b71e207b52e23546e180499be5bbb63a2f9c6e6"},
|
||||||
{file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6"},
|
{file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978f416bbff9da8d2091e3cf011c92da68b13f2c453dcc2e8109099b2a19d234"},
|
||||||
{file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a"},
|
{file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75fc593cf836f631153d0e21beaeb8d26e144445c73645889335c2247fcd71a0"},
|
||||||
{file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40"},
|
{file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d1528db3c7554f9d6eeb09df23cb80dd5177ec56eeb55cc5318826928de506"},
|
||||||
{file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7"},
|
{file = "orjson-3.9.14-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7183cc68ee2113b19b0b8714221e5e3b07b3ba10ca2bb108d78fd49cefaae101"},
|
||||||
{file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1"},
|
{file = "orjson-3.9.14-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:df3266d54246cb56b8bb17fa908660d2a0f2e3f63fbc32451ffc1b1505051d07"},
|
||||||
{file = "orjson-3.9.15-cp38-none-win32.whl", hash = "sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5"},
|
{file = "orjson-3.9.14-cp38-none-win32.whl", hash = "sha256:7913079b029e1b3501854c9a78ad938ed40d61fe09bebab3c93e60ff1301b189"},
|
||||||
{file = "orjson-3.9.15-cp38-none-win_amd64.whl", hash = "sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b"},
|
{file = "orjson-3.9.14-cp38-none-win_amd64.whl", hash = "sha256:29512eb925b620e5da2fd7585814485c67cc6ba4fe739a0a700c50467a8a8065"},
|
||||||
{file = "orjson-3.9.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e"},
|
{file = "orjson-3.9.14-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5bf597530544db27a8d76aced49cfc817ee9503e0a4ebf0109cd70331e7bbe0c"},
|
||||||
{file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180"},
|
{file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac650d49366fa41fe702e054cb560171a8634e2865537e91f09a8d05ea5b1d37"},
|
||||||
{file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd"},
|
{file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:236230433a9a4968ab895140514c308fdf9f607cb8bee178a04372b771123860"},
|
||||||
{file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb"},
|
{file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3014ccbda9be0b1b5f8ea895121df7e6524496b3908f4397ff02e923bcd8f6dd"},
|
||||||
{file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262"},
|
{file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac0c7eae7ad3a223bde690565442f8a3d620056bd01196f191af8be58a5248e1"},
|
||||||
{file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790"},
|
{file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca33fdd0b38839b01912c57546d4f412ba7bfa0faf9bf7453432219aec2df07"},
|
||||||
{file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b"},
|
{file = "orjson-3.9.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f75823cc1674a840a151e999a7dfa0d86c911150dd6f951d0736ee9d383bf415"},
|
||||||
{file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10"},
|
{file = "orjson-3.9.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f52ac2eb49e99e7373f62e2a68428c6946cda52ce89aa8fe9f890c7278e2d3a"},
|
||||||
{file = "orjson-3.9.15-cp39-none-win32.whl", hash = "sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a"},
|
{file = "orjson-3.9.14-cp39-none-win32.whl", hash = "sha256:0572f174f50b673b7df78680fb52cd0087a8585a6d06d295a5f790568e1064c6"},
|
||||||
{file = "orjson-3.9.15-cp39-none-win_amd64.whl", hash = "sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7"},
|
{file = "orjson-3.9.14-cp39-none-win_amd64.whl", hash = "sha256:ab90c02cb264250b8a58cedcc72ed78a4a257d956c8d3c8bebe9751b818dfad8"},
|
||||||
{file = "orjson-3.9.15.tar.gz", hash = "sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061"},
|
{file = "orjson-3.9.14.tar.gz", hash = "sha256:06fb40f8e49088ecaa02f1162581d39e2cf3fd9dbbfe411eb2284147c99bad79"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2465,13 +2465,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.0.2"
|
version = "8.0.0"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"},
|
{file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"},
|
||||||
{file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"},
|
{file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2836,28 +2836,28 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.2.2"
|
version = "0.2.1"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"},
|
{file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"},
|
||||||
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"},
|
{file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"},
|
||||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"},
|
{file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"},
|
||||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"},
|
{file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"},
|
||||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"},
|
{file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"},
|
||||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"},
|
{file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"},
|
||||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"},
|
{file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"},
|
||||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"},
|
{file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"},
|
||||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"},
|
{file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"},
|
||||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"},
|
{file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"},
|
||||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"},
|
{file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"},
|
||||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"},
|
{file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"},
|
||||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"},
|
{file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"},
|
||||||
{file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"},
|
{file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"},
|
||||||
{file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"},
|
{file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"},
|
||||||
{file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"},
|
{file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"},
|
||||||
{file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"},
|
{file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.96.0"
|
version = "1.95.1"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 124,
|
"android.injected.version.code" => 123,
|
||||||
"android.injected.version.name" => "1.96.0",
|
"android.injected.version.name" => "1.95.1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Beta"
|
desc "iOS Beta"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.96.0"
|
version_number: "1.95.1"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class VideoViewerPage extends HookWidget {
|
|||||||
controlsSafeAreaMinimum: const EdgeInsets.only(
|
controlsSafeAreaMinimum: const EdgeInsets.only(
|
||||||
bottom: 100,
|
bottom: 100,
|
||||||
),
|
),
|
||||||
placeholder: SizedBox.expand(child: placeholder),
|
placeholder: placeholder,
|
||||||
showControls: showControls && !isMotionVideo,
|
showControls: showControls && !isMotionVideo,
|
||||||
hideControlsTimer: hideControlsTimer,
|
hideControlsTimer: hideControlsTimer,
|
||||||
customControls: const VideoPlayerControls(),
|
customControls: const VideoPlayerControls(),
|
||||||
@@ -58,7 +58,7 @@ class VideoViewerPage extends HookWidget {
|
|||||||
if (controller == null) {
|
if (controller == null) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
if (placeholder != null) SizedBox.expand(child: placeholder!),
|
if (placeholder != null) placeholder!,
|
||||||
const DelayedLoadingIndicator(
|
const DelayedLoadingIndicator(
|
||||||
fadeInDuration: Duration(milliseconds: 500),
|
fadeInDuration: Duration(milliseconds: 500),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/hooks/blurhash_hook.dart';
|
||||||
|
|
||||||
|
class BlurhashThumb extends HookWidget {
|
||||||
|
final double height;
|
||||||
|
final double width;
|
||||||
|
final Asset asset;
|
||||||
|
final EdgeInsets margin;
|
||||||
|
|
||||||
|
const BlurhashThumb({
|
||||||
|
super.key,
|
||||||
|
required this.height,
|
||||||
|
required this.width,
|
||||||
|
required this.asset,
|
||||||
|
required this.margin,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final blurhash = useBlurHashRef(asset).value;
|
||||||
|
if (blurhash == null) {
|
||||||
|
return SizedBox(
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Padding(
|
||||||
|
padding: margin,
|
||||||
|
child: Image.memory(
|
||||||
|
blurhash,
|
||||||
|
gaplessPlayback: true,
|
||||||
|
frameBuilder: (
|
||||||
|
BuildContext context,
|
||||||
|
Widget child,
|
||||||
|
int? frame,
|
||||||
|
bool wasSynchronouslyLoaded,
|
||||||
|
) {
|
||||||
|
if (wasSynchronouslyLoaded) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
child: frame != null
|
||||||
|
? child
|
||||||
|
: SizedBox(
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
@@ -6,12 +7,15 @@ import 'package:collection/collection.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/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/home/ui/asset_grid/blurhash_thumb.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/hooks/blurhash_hook.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
|
|
||||||
import 'asset_grid_data_structure.dart';
|
import 'asset_grid_data_structure.dart';
|
||||||
@@ -325,32 +329,33 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A single row of all placeholder widgets
|
/// A single row of all placeholder widgets
|
||||||
class _PlaceholderRow extends StatelessWidget {
|
class _PlaceholderRow extends HookWidget {
|
||||||
final int number;
|
|
||||||
final double width;
|
final double width;
|
||||||
final double height;
|
final double height;
|
||||||
final double margin;
|
final double margin;
|
||||||
|
final List<Asset> assets;
|
||||||
|
|
||||||
const _PlaceholderRow({
|
const _PlaceholderRow({
|
||||||
super.key,
|
super.key,
|
||||||
required this.number,
|
|
||||||
required this.width,
|
required this.width,
|
||||||
required this.height,
|
required this.height,
|
||||||
required this.margin,
|
required this.margin,
|
||||||
|
required this.assets,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < number; i++)
|
for (int i = 0; i < assets.length; i++)
|
||||||
ThumbnailPlaceholder(
|
BlurhashThumb(
|
||||||
key: ValueKey(i),
|
key: ValueKey(i),
|
||||||
|
asset: assets[i],
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
bottom: margin,
|
bottom: margin,
|
||||||
right: i + 1 == number ? 0.0 : margin,
|
right: i + 1 == assets.length ? 0.0 : margin,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -401,9 +406,9 @@ class _Section extends StatelessWidget {
|
|||||||
final width = constraints.maxWidth / assetsPerRow -
|
final width = constraints.maxWidth / assetsPerRow -
|
||||||
margin * (assetsPerRow - 1) / assetsPerRow;
|
margin * (assetsPerRow - 1) / assetsPerRow;
|
||||||
final rows = (section.count + assetsPerRow - 1) ~/ assetsPerRow;
|
final rows = (section.count + assetsPerRow - 1) ~/ assetsPerRow;
|
||||||
final List<Asset> assetsToRender = scrolling
|
final List<Asset> assetsToRender = //scrolling
|
||||||
? []
|
//? []
|
||||||
: renderList.loadAssets(section.offset, section.count);
|
renderList.loadAssets(section.offset, section.count);
|
||||||
return Column(
|
return Column(
|
||||||
key: ValueKey(section.offset),
|
key: ValueKey(section.offset),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -422,18 +427,22 @@ class _Section extends StatelessWidget {
|
|||||||
selectAssets: selectAssets,
|
selectAssets: selectAssets,
|
||||||
deselectAssets: deselectAssets,
|
deselectAssets: deselectAssets,
|
||||||
),
|
),
|
||||||
for (int i = 0; i < rows; i++)
|
Stack(
|
||||||
scrolling
|
children: [
|
||||||
? _PlaceholderRow(
|
for (int i = 0; i < rows; i++)
|
||||||
key: ValueKey(i),
|
_PlaceholderRow(
|
||||||
number: i + 1 == rows
|
key: ValueKey('placeholder-$i'),
|
||||||
? section.count - i * assetsPerRow
|
assets: assetsToRender.nestedSlice(
|
||||||
: assetsPerRow,
|
i * assetsPerRow,
|
||||||
width: width,
|
min((i + 1) * assetsPerRow, section.count),
|
||||||
height: width,
|
),
|
||||||
margin: margin,
|
width: width,
|
||||||
)
|
height: width,
|
||||||
: _AssetRow(
|
margin: margin,
|
||||||
|
),
|
||||||
|
if (!scrolling)
|
||||||
|
for (int i = 0; i < rows; i++)
|
||||||
|
_AssetRow(
|
||||||
key: ValueKey(i),
|
key: ValueKey(i),
|
||||||
assets: assetsToRender.nestedSlice(
|
assets: assetsToRender.nestedSlice(
|
||||||
i * assetsPerRow,
|
i * assetsPerRow,
|
||||||
@@ -454,6 +463,8 @@ class _Section extends StatelessWidget {
|
|||||||
onSelect: (asset) => selectAssets([asset]),
|
onSelect: (asset) => selectAssets([asset]),
|
||||||
onDeselect: (asset) => deselectAssets([asset]),
|
onDeselect: (asset) => deselectAssets([asset]),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/ui/hooks/blurhash_hook.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_thumbnail.dart';
|
||||||
|
|
||||||
class MemoryCard extends StatelessWidget {
|
class MemoryCard extends StatelessWidget {
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
@@ -22,6 +22,8 @@ class MemoryCard extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
String get accessToken => Store.get(StoreKey.accessToken);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
@@ -36,8 +38,19 @@ class MemoryCard extends StatelessWidget {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
SizedBox.expand(
|
ImageFiltered(
|
||||||
child: _BlurredBackdrop(asset: asset),
|
imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: ImmichThumbnail.imageProvider(
|
||||||
|
asset: asset,
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Container(color: Colors.black.withOpacity(0.2)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
@@ -100,50 +113,3 @@ class MemoryCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BlurredBackdrop extends HookWidget {
|
|
||||||
final Asset asset;
|
|
||||||
|
|
||||||
const _BlurredBackdrop({required this.asset});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final blurhash = useBlurHashRef(asset).value;
|
|
||||||
if (blurhash != null) {
|
|
||||||
// Use a nice cheap blur hash image decoration
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
image: MemoryImage(
|
|
||||||
blurhash,
|
|
||||||
),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
color: Colors.black.withOpacity(0.2),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Fall back to using a more expensive image filtered
|
|
||||||
// Since the ImmichImage is already precached, we can
|
|
||||||
// safely use that as the image provider
|
|
||||||
return ImageFiltered(
|
|
||||||
imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
image: ImmichImage.imageProvider(
|
|
||||||
asset: asset,
|
|
||||||
),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
color: Colors.black.withOpacity(0.2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:immich_mobile/modules/memories/ui/memory_epilogue.dart';
|
|||||||
import 'package:immich_mobile/modules/memories/ui/memory_progress_indicator.dart';
|
import 'package:immich_mobile/modules/memories/ui/memory_progress_indicator.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_thumbnail.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class MemoryPage extends HookConsumerWidget {
|
class MemoryPage extends HookConsumerWidget {
|
||||||
@@ -109,13 +110,24 @@ class MemoryPage extends HookConsumerWidget {
|
|||||||
asset = memories[nextMemoryIndex].assets.first;
|
asset = memories[nextMemoryIndex].assets.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Precache the asset
|
// Gets the thumbnail url and precaches it
|
||||||
await precacheImage(
|
final precaches = <Future<dynamic>>[];
|
||||||
ImmichImage.imageProvider(
|
|
||||||
asset: asset,
|
precaches.addAll([
|
||||||
|
precacheImage(
|
||||||
|
ImmichImage.imageProvider(
|
||||||
|
asset: asset,
|
||||||
|
),
|
||||||
|
context,
|
||||||
),
|
),
|
||||||
context,
|
precacheImage(
|
||||||
);
|
ImmichThumbnail.imageProvider(
|
||||||
|
asset: asset,
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
await Future.wait(precaches);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Precache the next page right away if we are on the first page
|
// Precache the next page right away if we are on the first page
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ OctoPlaceholderBuilder blurHashPlaceholderBuilder(
|
|||||||
}) {
|
}) {
|
||||||
return (context) => blurhash == null
|
return (context) => blurhash == null
|
||||||
? const ThumbnailPlaceholder()
|
? const ThumbnailPlaceholder()
|
||||||
: FadeInPlaceholderImage(
|
: Image(
|
||||||
placeholder: const ThumbnailPlaceholder(),
|
|
||||||
image: MemoryImage(blurhash),
|
image: MemoryImage(blurhash),
|
||||||
fit: fit ?? BoxFit.cover,
|
fit: fit ?? BoxFit.cover,
|
||||||
);
|
);
|
||||||
|
|||||||
Generated
+1
-1
@@ -3,7 +3,7 @@ Immich API
|
|||||||
|
|
||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 1.96.0
|
- API version: 1.95.1
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|||||||
+16
-40
@@ -413,10 +413,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "6.1.4"
|
||||||
file_selector_linux:
|
file_selector_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -860,30 +860,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.1"
|
version: "4.8.1"
|
||||||
leak_tracker:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker
|
|
||||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "10.0.0"
|
|
||||||
leak_tracker_flutter_testing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker_flutter_testing
|
|
||||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.1"
|
|
||||||
leak_tracker_testing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker_testing
|
|
||||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.1"
|
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -931,18 +907,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.16+1"
|
version: "0.12.16"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.5.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
@@ -1026,10 +1002,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.8.3"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1162,10 +1138,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.2"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1194,10 +1170,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: process
|
name: process
|
||||||
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.2"
|
version: "4.2.4"
|
||||||
provider:
|
provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1663,10 +1639,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "13.0.0"
|
version: "11.10.0"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1711,10 +1687,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webdriver
|
name: webdriver
|
||||||
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
|
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.2"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 1.96.0+124
|
version: 1.95.1+123
|
||||||
isar_version: &isar_version 3.1.0+1
|
isar_version: &isar_version 3.1.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -6458,7 +6458,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.96.0",
|
"version": "1.95.1",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.96.0
|
* The version of the OpenAPI document: 1.95.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.96.0
|
* The version of the OpenAPI document: 1.95.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.96.0
|
* The version of the OpenAPI document: 1.95.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.96.0
|
* The version of the OpenAPI document: 1.95.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.96.0
|
* The version of the OpenAPI document: 1.95.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.96.0
|
* 1.95.1
|
||||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||||
* See https://www.npmjs.com/package/oazapfts
|
* See https://www.npmjs.com/package/oazapfts
|
||||||
*/
|
*/
|
||||||
|
|||||||
+6
-6
@@ -23,15 +23,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@oazapfts/runtime": {
|
"node_modules/@oazapfts/runtime": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@oazapfts/runtime/-/runtime-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@oazapfts/runtime/-/runtime-1.0.0.tgz",
|
||||||
"integrity": "sha512-CMl7f1gXYpjIyEtDhg4YfXwr2MXfbadbvqwKbMsaHkVtSglmuz5A8jSyefTqaJlmh0MOA2ZNS9jnbfIdtcoDiw==",
|
"integrity": "sha512-1ovqeaeEvShbYge5/7ctJokpvqB0anBdfDNfU5jWstjV2/Gbe+vvcBM274Z0abM3IM0b9MmSNWYBXnJXYO8KCw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.11.20",
|
"version": "20.11.19",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
|
||||||
"integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
|
"integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
# dev build
|
# dev build
|
||||||
FROM ghcr.io/immich-app/base-server-dev:20240227@sha256:b1e212c106ce2318a587e0b2ef377215c958e877f61993ed9310534e4589cce4 as dev
|
FROM ghcr.io/immich-app/base-server-dev:20240222@sha256:2ff467d6ae5c00a2317eb7b13cb40ba5be0fd33c160175dba621b1bf72bc1cd1 as dev
|
||||||
|
|
||||||
RUN apt-get install --no-install-recommends -yqq tini
|
RUN apt-get install --no-install-recommends -yqq tini
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
@@ -40,7 +40,7 @@ RUN npm run build
|
|||||||
|
|
||||||
|
|
||||||
# prod build
|
# prod build
|
||||||
FROM ghcr.io/immich-app/base-server-prod:20240227@sha256:d47f5f7f2b6c53957c6353352b2fa24f2845da50e6491a7c74eb779ace10628c
|
FROM ghcr.io/immich-app/base-server-prod:20240222@sha256:9ae5eebf95cf7759eec9dcfbd9e48a722701075ac855209f2e0b01c631b76f5c
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
|
|||||||
@@ -538,6 +538,90 @@ describe(`${AssetController.name} (e2e)`, () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GET /asset/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(server).get(`/asset/${uuidStub.notFound}`);
|
||||||
|
expect(body).toEqual(errorStub.unauthorized);
|
||||||
|
expect(status).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid id', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get(`/asset/${uuidStub.invalid}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorStub.badRequest(['id must be a UUID']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require access', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get(`/asset/${asset4.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorStub.noPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the asset info', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toMatchObject({ id: asset1.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with a shared link', async () => {
|
||||||
|
const sharedLink = await api.sharedLinkApi.create(server, user1.accessToken, {
|
||||||
|
type: SharedLinkType.INDIVIDUAL,
|
||||||
|
assetIds: [asset1.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { status, body } = await request(server).get(`/asset/${asset1.id}?key=${sharedLink.key}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toMatchObject({ id: asset1.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not send people data for shared links for un-authenticated users', async () => {
|
||||||
|
const personRepository = app.get<IPersonRepository>(IPersonRepository);
|
||||||
|
const person = await personRepository.create({ ownerId: asset1.ownerId, name: 'Test Person' });
|
||||||
|
|
||||||
|
await personRepository.createFaces([
|
||||||
|
{
|
||||||
|
assetId: asset1.id,
|
||||||
|
personId: person.id,
|
||||||
|
embedding: Array.from({ length: 512 }, Math.random),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ isFavorite: true });
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: asset1.id,
|
||||||
|
isFavorite: true,
|
||||||
|
people: [
|
||||||
|
{
|
||||||
|
birthDate: null,
|
||||||
|
id: expect.any(String),
|
||||||
|
isHidden: false,
|
||||||
|
name: 'Test Person',
|
||||||
|
thumbnailPath: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const sharedLink = await api.sharedLinkApi.create(server, user1.accessToken, {
|
||||||
|
type: SharedLinkType.INDIVIDUAL,
|
||||||
|
assetIds: [asset1.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await request(server).get(`/asset/${asset1.id}?key=${sharedLink.key}`);
|
||||||
|
expect(data.status).toBe(200);
|
||||||
|
expect(data.body).toMatchObject({ people: [] });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('POST /asset/upload', () => {
|
describe('POST /asset/upload', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(server)
|
||||||
@@ -675,6 +759,286 @@ describe(`${AssetController.name} (e2e)`, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PUT /asset/:id', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(server).put(`/asset/:${uuidStub.notFound}`);
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorStub.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a valid id', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${uuidStub.invalid}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorStub.badRequest(['id must be a UUID']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require access', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset4.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorStub.noPermission);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should favorite an asset', async () => {
|
||||||
|
expect(asset1).toMatchObject({ isFavorite: false });
|
||||||
|
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ isFavorite: true });
|
||||||
|
expect(body).toMatchObject({ id: asset1.id, isFavorite: true });
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should archive an asset', async () => {
|
||||||
|
expect(asset1).toMatchObject({ isArchived: false });
|
||||||
|
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ isArchived: true });
|
||||||
|
expect(body).toMatchObject({ id: asset1.id, isArchived: true });
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update date time original', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ dateTimeOriginal: '2023-11-19T18:11:00.000-07:00' });
|
||||||
|
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: asset1.id,
|
||||||
|
exifInfo: expect.objectContaining({ dateTimeOriginal: '2023-11-20T01:11:00.000Z' }),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid gps coordinates', async () => {
|
||||||
|
for (const test of [
|
||||||
|
{ latitude: 12 },
|
||||||
|
{ longitude: 12 },
|
||||||
|
{ latitude: 12, longitude: 'abc' },
|
||||||
|
{ latitude: 'abc', longitude: 12 },
|
||||||
|
{ latitude: null, longitude: 12 },
|
||||||
|
{ latitude: 12, longitude: null },
|
||||||
|
{ latitude: 91, longitude: 12 },
|
||||||
|
{ latitude: -91, longitude: 12 },
|
||||||
|
{ latitude: 12, longitude: -181 },
|
||||||
|
{ latitude: 12, longitude: 181 },
|
||||||
|
]) {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.send(test)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorStub.badRequest());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update gps data', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ latitude: 12, longitude: 12 });
|
||||||
|
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: asset1.id,
|
||||||
|
exifInfo: expect.objectContaining({ latitude: 12, longitude: 12 }),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the description', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ description: 'Test asset description' });
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: asset1.id,
|
||||||
|
exifInfo: expect.objectContaining({ description: 'Test asset description' }),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return tagged people', async () => {
|
||||||
|
const personRepository = app.get<IPersonRepository>(IPersonRepository);
|
||||||
|
const person = await personRepository.create({ ownerId: asset1.ownerId, name: 'Test Person' });
|
||||||
|
|
||||||
|
await personRepository.createFaces([
|
||||||
|
{
|
||||||
|
assetId: asset1.id,
|
||||||
|
personId: person.id,
|
||||||
|
embedding: Array.from({ length: 512 }, Math.random),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ isFavorite: true });
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: asset1.id,
|
||||||
|
isFavorite: true,
|
||||||
|
people: [
|
||||||
|
{
|
||||||
|
birthDate: null,
|
||||||
|
id: expect.any(String),
|
||||||
|
isHidden: false,
|
||||||
|
name: 'Test Person',
|
||||||
|
thumbnailPath: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /asset/statistics', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await api.assetApi.upload(server, user1.accessToken, 'favored_asset', { isFavorite: true });
|
||||||
|
await api.assetApi.upload(server, user1.accessToken, 'archived_asset', { isArchived: true });
|
||||||
|
await api.assetApi.upload(server, user1.accessToken, 'favored_archived_asset', {
|
||||||
|
isFavorite: true,
|
||||||
|
isArchived: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(server).get('/asset/statistics');
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorStub.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return stats of all assets', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get('/asset/statistics')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
|
expect(body).toEqual({ images: 6, videos: 1, total: 7 });
|
||||||
|
expect(status).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return stats of all favored assets', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get('/asset/statistics')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.query({ isFavorite: true });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({ images: 2, videos: 1, total: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return stats of all archived assets', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get('/asset/statistics')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.query({ isArchived: true });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({ images: 3, videos: 0, total: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return stats of all favored and archived assets', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get('/asset/statistics')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.query({ isFavorite: true, isArchived: true });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({ images: 1, videos: 0, total: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return stats of all assets neither favored nor archived', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get('/asset/statistics')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.query({ isFavorite: false, isArchived: false });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({ images: 2, videos: 0, total: 2 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /asset/random', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await Promise.all([
|
||||||
|
createAsset(user1, new Date('1970-02-01')),
|
||||||
|
createAsset(user1, new Date('1970-02-01')),
|
||||||
|
createAsset(user1, new Date('1970-02-01')),
|
||||||
|
createAsset(user1, new Date('1970-02-01')),
|
||||||
|
createAsset(user1, new Date('1970-02-01')),
|
||||||
|
createAsset(user1, new Date('1970-02-01')),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(server).get('/asset/random');
|
||||||
|
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorStub.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(Array(10))('should return 1 random assets', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get('/asset/random')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
|
||||||
|
const assets: AssetResponseDto[] = body;
|
||||||
|
expect(assets.length).toBe(1);
|
||||||
|
expect(assets[0].ownerId).toBe(user1.userId);
|
||||||
|
//
|
||||||
|
// assets owned by user2
|
||||||
|
expect(assets[0].id).not.toBe(asset4.id);
|
||||||
|
// assets owned by user1
|
||||||
|
expect([asset1.id, asset2.id, asset3.id]).toContain(assets[0].id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(Array(10))('should return 2 random assets', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get('/asset/random?count=2')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
|
||||||
|
const assets: AssetResponseDto[] = body;
|
||||||
|
expect(assets.length).toBe(2);
|
||||||
|
|
||||||
|
for (const asset of assets) {
|
||||||
|
expect(asset.ownerId).toBe(user1.userId);
|
||||||
|
// assets owned by user1
|
||||||
|
expect([asset1.id, asset2.id, asset3.id]).toContain(asset.id);
|
||||||
|
// assets owned by user2
|
||||||
|
expect(asset.id).not.toBe(asset4.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(Array(10))(
|
||||||
|
'should return 1 asset if there are 10 assets in the database but user 2 only has 1',
|
||||||
|
async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.get('/[]asset/random')
|
||||||
|
.set('Authorization', `Bearer ${user2.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual([expect.objectContaining({ id: asset4.id })]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should return error', async () => {
|
||||||
|
const { status } = await request(server)
|
||||||
|
.get('/asset/random?count=ABC')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(400);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('GET /asset/time-buckets', () => {
|
describe('GET /asset/time-buckets', () => {
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
const { status, body } = await request(server).get('/asset/time-buckets').query({ size: TimeBucketSize.MONTH });
|
const { status, body } = await request(server).get('/asset/time-buckets').query({ size: TimeBucketSize.MONTH });
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AssetResponseDto } from '@app/domain';
|
import { AssetBulkDeleteDto, AssetResponseDto } from '@app/domain';
|
||||||
import { CreateAssetDto } from '@app/immich/api-v1/asset/dto/create-asset.dto';
|
import { CreateAssetDto } from '@app/immich/api-v1/asset/dto/create-asset.dto';
|
||||||
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
|
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
|
||||||
import { randomBytes } from 'node:crypto';
|
import { randomBytes } from 'node:crypto';
|
||||||
@@ -74,4 +74,8 @@ export const assetApi = {
|
|||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
return body;
|
return body;
|
||||||
},
|
},
|
||||||
|
delete: async (server: any, accessToken: string, dto: AssetBulkDeleteDto) => {
|
||||||
|
const { status } = await request(server).delete('/asset').set('Authorization', `Bearer ${accessToken}`).send(dto);
|
||||||
|
expect(status).toBe(204);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { LoginResponseDto } from '@app/domain';
|
||||||
|
import { api } from 'e2e/client';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import { basename, join } from 'node:path';
|
||||||
|
import type { App } from 'supertest/types';
|
||||||
|
import { IMMICH_TEST_ASSET_PATH, testApp } from '../../../src/test-utils/utils';
|
||||||
|
|
||||||
|
const assetFilePath = join(IMMICH_TEST_ASSET_PATH, 'formats/png/density_plot.png');
|
||||||
|
|
||||||
|
describe(`Trash (e2e)`, () => {
|
||||||
|
let server: App;
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const app = await testApp.create();
|
||||||
|
server = app.getHttpServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await testApp.reset();
|
||||||
|
await api.authApi.adminSignUp(server);
|
||||||
|
admin = await api.authApi.adminLogin(server);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await testApp.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move an asset to trash', async () => {
|
||||||
|
const content = await readFile(assetFilePath);
|
||||||
|
const { id: assetId } = await api.assetApi.upload(server, admin.accessToken, 'test-device-id', {
|
||||||
|
content,
|
||||||
|
filename: basename(assetFilePath),
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadedAsset = await api.assetApi.get(server, admin.accessToken, assetId);
|
||||||
|
expect(uploadedAsset.isTrashed).toBe(false);
|
||||||
|
|
||||||
|
await api.assetApi.delete(server, admin.accessToken, { ids: [assetId] });
|
||||||
|
|
||||||
|
const deletedAsset = await api.assetApi.get(server, admin.accessToken, assetId);
|
||||||
|
expect(deletedAsset.isTrashed).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete all trashed assets', async () => {
|
||||||
|
const content = await readFile(assetFilePath);
|
||||||
|
const { id: assetId } = await api.assetApi.upload(server, admin.accessToken, 'test-device-id', {
|
||||||
|
content,
|
||||||
|
filename: basename(assetFilePath),
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.assetApi.delete(server, admin.accessToken, { ids: [assetId] });
|
||||||
|
|
||||||
|
const assetsBeforeEmpty = await api.assetApi.getAllAssets(server, admin.accessToken);
|
||||||
|
expect(assetsBeforeEmpty.length).toBe(1);
|
||||||
|
|
||||||
|
await api.trashApi.empty(server, admin.accessToken);
|
||||||
|
|
||||||
|
const assetsAfterEmpty = await api.assetApi.getAllAssets(server, admin.accessToken);
|
||||||
|
expect(assetsAfterEmpty.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should restore all trashed assets', async () => {
|
||||||
|
const content = await readFile(assetFilePath);
|
||||||
|
const { id: assetId } = await api.assetApi.upload(server, admin.accessToken, 'test-device-id', {
|
||||||
|
content,
|
||||||
|
filename: basename(assetFilePath),
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.assetApi.delete(server, admin.accessToken, { ids: [assetId] });
|
||||||
|
|
||||||
|
const deletedAsset = await api.assetApi.get(server, admin.accessToken, assetId);
|
||||||
|
expect(deletedAsset.isTrashed).toBe(true);
|
||||||
|
|
||||||
|
await api.trashApi.restore(server, admin.accessToken);
|
||||||
|
|
||||||
|
const restoredAsset = await api.assetApi.get(server, admin.accessToken, assetId);
|
||||||
|
expect(restoredAsset.isTrashed).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
Generated
+110
-110
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.96.0",
|
"version": "1.95.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.96.0",
|
"version": "1.95.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.22.11",
|
"@babel/runtime": "^7.22.11",
|
||||||
@@ -3179,9 +3179,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.11.20",
|
"version": "20.11.19",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
|
||||||
"integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
|
"integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
@@ -3281,9 +3281,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.8",
|
"version": "7.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
|
||||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
"integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/send": {
|
"node_modules/@types/send": {
|
||||||
@@ -3398,16 +3398,16 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz",
|
||||||
"integrity": "sha512-/XtVZJtbaphtdrWjr+CJclaCVGPtOdBpFEnvtNf/jRV0IiEemRrL0qABex/nEt8isYcnFacm3nPHYQwL+Wb7qg==",
|
"integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.5.1",
|
"@eslint-community/regexpp": "^4.5.1",
|
||||||
"@typescript-eslint/scope-manager": "7.0.2",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/type-utils": "7.0.2",
|
"@typescript-eslint/type-utils": "7.0.1",
|
||||||
"@typescript-eslint/utils": "7.0.2",
|
"@typescript-eslint/utils": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.0.2",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.2.4",
|
"ignore": "^5.2.4",
|
||||||
@@ -3433,15 +3433,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz",
|
||||||
"integrity": "sha512-GdwfDglCxSmU+QTS9vhz2Sop46ebNCXpPPvsByK7hu0rFGRHL+AusKQJ7SoN+LbLh6APFpQwHKmDSwN35Z700Q==",
|
"integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "7.0.2",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/typescript-estree": "7.0.2",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.0.2",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3461,13 +3461,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz",
|
||||||
"integrity": "sha512-l6sa2jF3h+qgN2qUMjVR3uCNGjWw4ahGfzIYsCtFrQJCjhbrDPdiihYT8FnnqFwsWX+20hK592yX9I2rxKTP4g==",
|
"integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.0.2"
|
"@typescript-eslint/visitor-keys": "7.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^16.0.0 || >=18.0.0"
|
||||||
@@ -3478,13 +3478,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz",
|
||||||
"integrity": "sha512-IKKDcFsKAYlk8Rs4wiFfEwJTQlHcdn8CLwLaxwd6zb8HNiMcQIFX9sWax2k4Cjj7l7mGS5N1zl7RCHOVwHq2VQ==",
|
"integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "7.0.2",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"@typescript-eslint/utils": "7.0.2",
|
"@typescript-eslint/utils": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.0.1"
|
"ts-api-utils": "^1.0.1"
|
||||||
},
|
},
|
||||||
@@ -3505,9 +3505,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz",
|
||||||
"integrity": "sha512-ZzcCQHj4JaXFjdOql6adYV4B/oFOFjPOC9XYwCaZFRvqN8Llfvv4gSxrkQkd2u4Ci62i2c6W6gkDwQJDaRc4nA==",
|
"integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^16.0.0 || >=18.0.0"
|
||||||
@@ -3518,13 +3518,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz",
|
||||||
"integrity": "sha512-3AMc8khTcELFWcKcPc0xiLviEvvfzATpdPj/DXuOGIdQIIFybf4DMT1vKRbuAEOFMwhWt7NFLXRkbjsvKZQyvw==",
|
"integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.0.2",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -3570,17 +3570,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz",
|
||||||
"integrity": "sha512-PZPIONBIB/X684bhT1XlrkjNZJIEevwkKDsdwfiu1WeqBxYEEdIgVDgm8/bbKHVu+6YOpeRqcfImTdImx/4Bsw==",
|
"integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@types/json-schema": "^7.0.12",
|
"@types/json-schema": "^7.0.12",
|
||||||
"@types/semver": "^7.5.0",
|
"@types/semver": "^7.5.0",
|
||||||
"@typescript-eslint/scope-manager": "7.0.2",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/typescript-estree": "7.0.2",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"semver": "^7.5.4"
|
"semver": "^7.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3595,12 +3595,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz",
|
||||||
"integrity": "sha512-8Y+YiBmqPighbm5xA2k4wKTxRzx9EkBu7Rlw+WHqMvRJ3RPz/BMBO9b2ru0LUNmXg120PHUXD5+SWFy2R8DqlQ==",
|
"integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"eslint-visitor-keys": "^3.4.1"
|
"eslint-visitor-keys": "^3.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5504,9 +5504,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.5",
|
"version": "16.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz",
|
||||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
"integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -8139,9 +8139,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/joi": {
|
"node_modules/joi": {
|
||||||
"version": "17.12.2",
|
"version": "17.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz",
|
||||||
"integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==",
|
"integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/hoek": "^9.3.0",
|
"@hapi/hoek": "^9.3.0",
|
||||||
"@hapi/topo": "^5.1.0",
|
"@hapi/topo": "^5.1.0",
|
||||||
@@ -14730,9 +14730,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "20.11.20",
|
"version": "20.11.19",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
|
||||||
"integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
|
"integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
@@ -14819,9 +14819,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/semver": {
|
"@types/semver": {
|
||||||
"version": "7.5.8",
|
"version": "7.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
|
||||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
"integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/send": {
|
"@types/send": {
|
||||||
@@ -14936,16 +14936,16 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz",
|
||||||
"integrity": "sha512-/XtVZJtbaphtdrWjr+CJclaCVGPtOdBpFEnvtNf/jRV0IiEemRrL0qABex/nEt8isYcnFacm3nPHYQwL+Wb7qg==",
|
"integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@eslint-community/regexpp": "^4.5.1",
|
"@eslint-community/regexpp": "^4.5.1",
|
||||||
"@typescript-eslint/scope-manager": "7.0.2",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/type-utils": "7.0.2",
|
"@typescript-eslint/type-utils": "7.0.1",
|
||||||
"@typescript-eslint/utils": "7.0.2",
|
"@typescript-eslint/utils": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.0.2",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.2.4",
|
"ignore": "^5.2.4",
|
||||||
@@ -14955,54 +14955,54 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/parser": {
|
"@typescript-eslint/parser": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz",
|
||||||
"integrity": "sha512-GdwfDglCxSmU+QTS9vhz2Sop46ebNCXpPPvsByK7hu0rFGRHL+AusKQJ7SoN+LbLh6APFpQwHKmDSwN35Z700Q==",
|
"integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/scope-manager": "7.0.2",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/typescript-estree": "7.0.2",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.0.2",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/scope-manager": {
|
"@typescript-eslint/scope-manager": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz",
|
||||||
"integrity": "sha512-l6sa2jF3h+qgN2qUMjVR3uCNGjWw4ahGfzIYsCtFrQJCjhbrDPdiihYT8FnnqFwsWX+20hK592yX9I2rxKTP4g==",
|
"integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.0.2"
|
"@typescript-eslint/visitor-keys": "7.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/type-utils": {
|
"@typescript-eslint/type-utils": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz",
|
||||||
"integrity": "sha512-IKKDcFsKAYlk8Rs4wiFfEwJTQlHcdn8CLwLaxwd6zb8HNiMcQIFX9sWax2k4Cjj7l7mGS5N1zl7RCHOVwHq2VQ==",
|
"integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/typescript-estree": "7.0.2",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"@typescript-eslint/utils": "7.0.2",
|
"@typescript-eslint/utils": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.0.1"
|
"ts-api-utils": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/types": {
|
"@typescript-eslint/types": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz",
|
||||||
"integrity": "sha512-ZzcCQHj4JaXFjdOql6adYV4B/oFOFjPOC9XYwCaZFRvqN8Llfvv4gSxrkQkd2u4Ci62i2c6W6gkDwQJDaRc4nA==",
|
"integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz",
|
||||||
"integrity": "sha512-3AMc8khTcELFWcKcPc0xiLviEvvfzATpdPj/DXuOGIdQIIFybf4DMT1vKRbuAEOFMwhWt7NFLXRkbjsvKZQyvw==",
|
"integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.0.2",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -15032,27 +15032,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/utils": {
|
"@typescript-eslint/utils": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz",
|
||||||
"integrity": "sha512-PZPIONBIB/X684bhT1XlrkjNZJIEevwkKDsdwfiu1WeqBxYEEdIgVDgm8/bbKHVu+6YOpeRqcfImTdImx/4Bsw==",
|
"integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@types/json-schema": "^7.0.12",
|
"@types/json-schema": "^7.0.12",
|
||||||
"@types/semver": "^7.5.0",
|
"@types/semver": "^7.5.0",
|
||||||
"@typescript-eslint/scope-manager": "7.0.2",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/typescript-estree": "7.0.2",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"semver": "^7.5.4"
|
"semver": "^7.5.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/visitor-keys": {
|
"@typescript-eslint/visitor-keys": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz",
|
||||||
"integrity": "sha512-8Y+YiBmqPighbm5xA2k4wKTxRzx9EkBu7Rlw+WHqMvRJ3RPz/BMBO9b2ru0LUNmXg120PHUXD5+SWFy2R8DqlQ==",
|
"integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "7.0.2",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"eslint-visitor-keys": "^3.4.1"
|
"eslint-visitor-keys": "^3.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -16494,9 +16494,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dotenv": {
|
"dotenv": {
|
||||||
"version": "16.4.5",
|
"version": "16.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz",
|
||||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg=="
|
"integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg=="
|
||||||
},
|
},
|
||||||
"dotenv-expand": {
|
"dotenv-expand": {
|
||||||
"version": "10.0.0",
|
"version": "10.0.0",
|
||||||
@@ -18453,9 +18453,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"joi": {
|
"joi": {
|
||||||
"version": "17.12.2",
|
"version": "17.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz",
|
||||||
"integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==",
|
"integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@hapi/hoek": "^9.3.0",
|
"@hapi/hoek": "^9.3.0",
|
||||||
"@hapi/topo": "^5.1.0",
|
"@hapi/topo": "^5.1.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.96.0",
|
"version": "1.95.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -1801,7 +1801,7 @@ describe(MediaService.name, () => {
|
|||||||
{
|
{
|
||||||
inputOptions: [],
|
inputOptions: [],
|
||||||
outputOptions: [
|
outputOptions: [
|
||||||
`-c:v hevc_rkmpp`,
|
`-c:v hevc_rkmpp_encoder`,
|
||||||
'-c:a copy',
|
'-c:a copy',
|
||||||
'-movflags faststart',
|
'-movflags faststart',
|
||||||
'-fps_mode passthrough',
|
'-fps_mode passthrough',
|
||||||
@@ -1810,12 +1810,17 @@ describe(MediaService.name, () => {
|
|||||||
'-g 256',
|
'-g 256',
|
||||||
'-tag:v hvc1',
|
'-tag:v hvc1',
|
||||||
'-v verbose',
|
'-v verbose',
|
||||||
'-vf scale=-2:720,format=yuv420p',
|
|
||||||
'-level 153',
|
'-level 153',
|
||||||
'-rc_mode 3',
|
'-rc_mode 3',
|
||||||
|
'-quality_min 0',
|
||||||
|
'-quality_max 100',
|
||||||
'-b:v 10000k',
|
'-b:v 10000k',
|
||||||
|
'-width 1280',
|
||||||
|
'-height 720',
|
||||||
],
|
],
|
||||||
twoPass: false,
|
twoPass: false,
|
||||||
|
ffmpegPath: 'ffmpeg_mpp',
|
||||||
|
ldLibraryPath: '/lib/aarch64-linux-gnu:/lib/ffmpeg-mpp',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1836,7 +1841,7 @@ describe(MediaService.name, () => {
|
|||||||
{
|
{
|
||||||
inputOptions: [],
|
inputOptions: [],
|
||||||
outputOptions: [
|
outputOptions: [
|
||||||
`-c:v h264_rkmpp`,
|
`-c:v h264_rkmpp_encoder`,
|
||||||
'-c:a copy',
|
'-c:a copy',
|
||||||
'-movflags faststart',
|
'-movflags faststart',
|
||||||
'-fps_mode passthrough',
|
'-fps_mode passthrough',
|
||||||
@@ -1844,12 +1849,16 @@ describe(MediaService.name, () => {
|
|||||||
'-map 0:1',
|
'-map 0:1',
|
||||||
'-g 256',
|
'-g 256',
|
||||||
'-v verbose',
|
'-v verbose',
|
||||||
'-vf scale=-2:720,format=yuv420p',
|
|
||||||
'-level 51',
|
'-level 51',
|
||||||
'-rc_mode 2',
|
'-rc_mode 2',
|
||||||
'-qp_init 30',
|
'-quality_min 51',
|
||||||
|
'-quality_max 51',
|
||||||
|
'-width 1280',
|
||||||
|
'-height 720',
|
||||||
],
|
],
|
||||||
twoPass: false,
|
twoPass: false,
|
||||||
|
ffmpegPath: 'ffmpeg_mpp',
|
||||||
|
ldLibraryPath: '/lib/aarch64-linux-gnu:/lib/ffmpeg-mpp',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -607,6 +607,16 @@ export class VAAPIConfig extends BaseHWConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class RKMPPConfig extends BaseHWConfig {
|
export class RKMPPConfig extends BaseHWConfig {
|
||||||
|
getOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo): TranscodeOptions {
|
||||||
|
const options = super.getOptions(target, videoStream, audioStream);
|
||||||
|
options.ffmpegPath = 'ffmpeg_mpp';
|
||||||
|
options.ldLibraryPath = '/lib/aarch64-linux-gnu:/lib/ffmpeg-mpp';
|
||||||
|
if ([TranscodeTarget.ALL, TranscodeTarget.VIDEO].includes(target)) {
|
||||||
|
options.outputOptions.push(...this.getSizeOptions(videoStream));
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
eligibleForTwoPass(): boolean {
|
eligibleForTwoPass(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -618,6 +628,18 @@ export class RKMPPConfig extends BaseHWConfig {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilterOptions(videoStream: VideoStreamInfo) {
|
||||||
|
return this.shouldToneMap(videoStream) ? this.getToneMapping() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSizeOptions(videoStream: VideoStreamInfo) {
|
||||||
|
if (this.shouldScale(videoStream)) {
|
||||||
|
const { width, height } = this.getSize(videoStream);
|
||||||
|
return [`-width ${width}`, `-height ${height}`];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
getPresetOptions() {
|
getPresetOptions() {
|
||||||
switch (this.config.targetVideoCodec) {
|
switch (this.config.targetVideoCodec) {
|
||||||
case VideoCodec.H264: {
|
case VideoCodec.H264: {
|
||||||
@@ -637,11 +659,12 @@ export class RKMPPConfig extends BaseHWConfig {
|
|||||||
getBitrateOptions() {
|
getBitrateOptions() {
|
||||||
const bitrate = this.getMaxBitrateValue();
|
const bitrate = this.getMaxBitrateValue();
|
||||||
if (bitrate > 0) {
|
if (bitrate > 0) {
|
||||||
// -b:v specifies max bitrate, average bitrate is derived automatically...
|
return ['-rc_mode 3', '-quality_min 0', '-quality_max 100', `-b:v ${bitrate}${this.getBitrateUnit()}`];
|
||||||
return ['-rc_mode 3', `-b:v ${bitrate}${this.getBitrateUnit()}`];
|
} else {
|
||||||
|
// convert CQP from 51-10 to 0-100, values below 10 are set to 10
|
||||||
|
const quality = Math.floor(125 - Math.max(this.config.crf, 10) * (125 / 51));
|
||||||
|
return ['-rc_mode 2', `-quality_min ${quality}`, `-quality_max ${quality}`];
|
||||||
}
|
}
|
||||||
// use CRF value as QP value
|
|
||||||
return ['-rc_mode 2', `-qp_init ${this.config.crf}`];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportedCodecs() {
|
getSupportedCodecs() {
|
||||||
@@ -649,6 +672,6 @@ export class RKMPPConfig extends BaseHWConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getVideoCodec(): string {
|
getVideoCodec(): string {
|
||||||
return `${this.config.targetVideoCodec}_rkmpp`;
|
return `${this.config.targetVideoCodec}_rkmpp_encoder`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export interface TranscodeOptions {
|
|||||||
inputOptions: string[];
|
inputOptions: string[];
|
||||||
outputOptions: string[];
|
outputOptions: string[];
|
||||||
twoPass: boolean;
|
twoPass: boolean;
|
||||||
|
ffmpegPath?: string;
|
||||||
|
ldLibraryPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BitrateDistribution {
|
export interface BitrateDistribution {
|
||||||
|
|||||||
@@ -187,5 +187,4 @@ export interface ISearchRepository {
|
|||||||
searchFaces(search: FaceEmbeddingSearch): Promise<FaceSearchResult[]>;
|
searchFaces(search: FaceEmbeddingSearch): Promise<FaceSearchResult[]>;
|
||||||
upsert(smartInfo: Partial<SmartInfoEntity>, embedding?: Embedding): Promise<void>;
|
upsert(smartInfo: Partial<SmartInfoEntity>, embedding?: Embedding): Promise<void>;
|
||||||
searchPlaces(placeName: string): Promise<GeodataPlacesEntity[]>;
|
searchPlaces(placeName: string): Promise<GeodataPlacesEntity[]>;
|
||||||
deleteAllSearchEmbeddings(): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ describe(SmartInfoService.name, () => {
|
|||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.SMART_SEARCH, data: { id: assetStub.image.id } }]);
|
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.SMART_SEARCH, data: { id: assetStub.image.id } }]);
|
||||||
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.SMART_SEARCH);
|
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.SMART_SEARCH);
|
||||||
expect(searchMock.deleteAllSearchEmbeddings).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should queue all the assets', async () => {
|
it('should queue all the assets', async () => {
|
||||||
@@ -84,7 +83,6 @@ describe(SmartInfoService.name, () => {
|
|||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.SMART_SEARCH, data: { id: assetStub.image.id } }]);
|
expect(jobMock.queueAll).toHaveBeenCalledWith([{ name: JobName.SMART_SEARCH, data: { id: assetStub.image.id } }]);
|
||||||
expect(assetMock.getAll).toHaveBeenCalled();
|
expect(assetMock.getAll).toHaveBeenCalled();
|
||||||
expect(searchMock.deleteAllSearchEmbeddings).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -50,10 +50,6 @@ export class SmartInfoService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force) {
|
|
||||||
await this.repository.deleteAllSearchEmbeddings();
|
|
||||||
}
|
|
||||||
|
|
||||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
|
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => {
|
||||||
return force
|
return force
|
||||||
? this.assetRepository.getAll(pagination)
|
? this.assetRepository.getAll(pagination)
|
||||||
|
|||||||
@@ -76,7 +76,18 @@ export class MediaRepository implements IMediaRepository {
|
|||||||
transcode(input: string, output: string | Writable, options: TranscodeOptions): Promise<void> {
|
transcode(input: string, output: string | Writable, options: TranscodeOptions): Promise<void> {
|
||||||
if (!options.twoPass) {
|
if (!options.twoPass) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.configureFfmpegCall(input, output, options).on('error', reject).on('end', resolve).run();
|
const oldLdLibraryPath = process.env.LD_LIBRARY_PATH;
|
||||||
|
if (options.ldLibraryPath) {
|
||||||
|
// fluent ffmpeg does not allow to set environment variables, so we do it manually
|
||||||
|
process.env.LD_LIBRARY_PATH = this.chainPath(oldLdLibraryPath || '', options.ldLibraryPath);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.configureFfmpegCall(input, output, options).on('error', reject).on('end', resolve).run();
|
||||||
|
} finally {
|
||||||
|
if (options.ldLibraryPath) {
|
||||||
|
process.env.LD_LIBRARY_PATH = oldLdLibraryPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +121,7 @@ export class MediaRepository implements IMediaRepository {
|
|||||||
|
|
||||||
configureFfmpegCall(input: string, output: string | Writable, options: TranscodeOptions) {
|
configureFfmpegCall(input: string, output: string | Writable, options: TranscodeOptions) {
|
||||||
return ffmpeg(input, { niceness: 10 })
|
return ffmpeg(input, { niceness: 10 })
|
||||||
|
.setFfmpegPath(options.ffmpegPath || 'ffmpeg')
|
||||||
.inputOptions(options.inputOptions)
|
.inputOptions(options.inputOptions)
|
||||||
.outputOptions(options.outputOptions)
|
.outputOptions(options.outputOptions)
|
||||||
.output(output)
|
.output(output)
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ export class PersonRepository implements IPersonRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteAll(): Promise<void> {
|
async deleteAll(): Promise<void> {
|
||||||
await this.personRepository.clear();
|
await this.personRepository.delete({});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAllFaces(): Promise<void> {
|
async deleteAllFaces(): Promise<void> {
|
||||||
await this.assetFaceRepository.query('TRUNCATE TABLE asset_faces CASCADE');
|
await this.assetFaceRepository.delete({});
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllFaces(
|
getAllFaces(
|
||||||
|
|||||||
@@ -229,17 +229,25 @@ export class SearchRepository implements ISearchRepository {
|
|||||||
this.logger.log(`Updating database CLIP dimension size to ${dimSize}.`);
|
this.logger.log(`Updating database CLIP dimension size to ${dimSize}.`);
|
||||||
|
|
||||||
await this.smartSearchRepository.manager.transaction(async (manager) => {
|
await this.smartSearchRepository.manager.transaction(async (manager) => {
|
||||||
await manager.clear(SmartSearchEntity);
|
await manager.query(`DROP TABLE smart_search`);
|
||||||
await manager.query(`ALTER TABLE smart_search ALTER COLUMN embedding SET DATA TYPE vector(${dimSize})`);
|
|
||||||
|
await manager.query(`
|
||||||
|
CREATE TABLE smart_search (
|
||||||
|
"assetId" uuid PRIMARY KEY REFERENCES assets(id) ON DELETE CASCADE,
|
||||||
|
embedding vector(${dimSize}) NOT NULL )`);
|
||||||
|
|
||||||
|
await manager.query(`
|
||||||
|
CREATE INDEX clip_index ON smart_search
|
||||||
|
USING vectors (embedding vector_cos_ops) WITH (options = $$
|
||||||
|
[indexing.hnsw]
|
||||||
|
m = 16
|
||||||
|
ef_construction = 300
|
||||||
|
$$)`);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.log(`Successfully updated database CLIP dimension size from ${curDimSize} to ${dimSize}.`);
|
this.logger.log(`Successfully updated database CLIP dimension size from ${curDimSize} to ${dimSize}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteAllSearchEmbeddings(): Promise<void> {
|
|
||||||
return this.smartSearchRepository.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getDimSize(): Promise<number> {
|
private async getDimSize(): Promise<number> {
|
||||||
const res = await this.smartSearchRepository.manager.query(`
|
const res = await this.smartSearchRepository.manager.query(`
|
||||||
SELECT atttypmod as dimsize
|
SELECT atttypmod as dimsize
|
||||||
|
|||||||
@@ -8,6 +8,5 @@ export const newSearchRepositoryMock = (): jest.Mocked<ISearchRepository> => {
|
|||||||
searchFaces: jest.fn(),
|
searchFaces: jest.fn(),
|
||||||
upsert: jest.fn(),
|
upsert: jest.fn(),
|
||||||
searchPlaces: jest.fn(),
|
searchPlaces: jest.fn(),
|
||||||
deleteAllSearchEmbeddings: jest.fn(),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+7
-13
@@ -13,7 +13,6 @@ module.exports = {
|
|||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
ecmaVersion: 2022,
|
ecmaVersion: 2022,
|
||||||
extraFileExtensions: ['.svelte'],
|
extraFileExtensions: ['.svelte'],
|
||||||
project: ['./tsconfig.json'],
|
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
@@ -33,6 +32,13 @@ module.exports = {
|
|||||||
NodeJS: true,
|
NodeJS: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
'unicorn/no-useless-undefined': 'off',
|
||||||
|
'unicorn/prefer-spread': 'off',
|
||||||
|
'unicorn/no-null': 'off',
|
||||||
|
'unicorn/prevent-abbreviations': 'off',
|
||||||
|
'unicorn/no-nested-ternary': 'off',
|
||||||
|
'unicorn/consistent-function-scoping': 'off',
|
||||||
|
'unicorn/prefer-top-level-await': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'warn',
|
'warn',
|
||||||
{
|
{
|
||||||
@@ -42,17 +48,5 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
curly: 2,
|
curly: 2,
|
||||||
'unicorn/no-useless-undefined': 'off',
|
|
||||||
'unicorn/prefer-spread': 'off',
|
|
||||||
'unicorn/no-null': 'off',
|
|
||||||
'unicorn/prevent-abbreviations': 'off',
|
|
||||||
'unicorn/no-nested-ternary': 'off',
|
|
||||||
'unicorn/consistent-function-scoping': 'off',
|
|
||||||
'unicorn/prefer-top-level-await': 'off',
|
|
||||||
// TODO: set recommended-type-checked and remove these rules
|
|
||||||
'@typescript-eslint/await-thenable': 'error',
|
|
||||||
'@typescript-eslint/no-floating-promises': 'error',
|
|
||||||
'@typescript-eslint/no-misused-promises': 'error',
|
|
||||||
'@typescript-eslint/require-await': 'error',
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+171
-175
@@ -1,67 +1,67 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.2.0",
|
"version": "1.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.2.0",
|
"version": "1.1.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.3.67",
|
||||||
"@photo-sphere-viewer/core": "^5.7.1",
|
"@photo-sphere-viewer/core": "^5.7.0",
|
||||||
"@zoom-image/svelte": "^0.2.6",
|
"@zoom-image/svelte": "^0.2.0",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"copy-image-clipboard": "^2.1.2",
|
"copy-image-clipboard": "^2.1.2",
|
||||||
"dom-to-image": "^2.6.0",
|
"dom-to-image": "^2.6.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.7",
|
||||||
"justified-layout": "^4.1.0",
|
"justified-layout": "^4.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.2.1",
|
||||||
"socket.io-client": "^4.7.4",
|
"socket.io-client": "^4.6.1",
|
||||||
"svelte-local-storage-store": "^0.6.4",
|
"svelte-local-storage-store": "^0.6.0",
|
||||||
"svelte-maplibre": "^0.8.1",
|
"svelte-maplibre": "^0.8.0",
|
||||||
"thumbhash": "^0.1.1"
|
"thumbhash": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faker-js/faker": "^8.4.1",
|
"@faker-js/faker": "^8.0.0",
|
||||||
"@floating-ui/dom": "^1.6.3",
|
"@floating-ui/dom": "^1.5.1",
|
||||||
"@socket.io/component-emitter": "^3.1.0",
|
"@socket.io/component-emitter": "^3.1.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/enhanced-img": "^0.1.8",
|
"@sveltejs/enhanced-img": "^0.1.8",
|
||||||
"@sveltejs/kit": "^2.5.2",
|
"@sveltejs/kit": "^2.5.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||||
"@testing-library/jest-dom": "^6.4.2",
|
"@testing-library/jest-dom": "^6.1.5",
|
||||||
"@testing-library/svelte": "^4.1.0",
|
"@testing-library/svelte": "^4.0.3",
|
||||||
"@types/dom-to-image": "^2.6.7",
|
"@types/dom-to-image": "^2.6.4",
|
||||||
"@types/justified-layout": "^4.1.4",
|
"@types/justified-layout": "^4.1.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.6",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.2.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^7.1.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"@vitest/coverage-v8": "^1.3.1",
|
"@vitest/coverage-v8": "^1.0.4",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.13",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.34.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.35.1",
|
"eslint-plugin-svelte": "^2.30.0",
|
||||||
"eslint-plugin-unicorn": "^51.0.1",
|
"eslint-plugin-unicorn": "^51.0.0",
|
||||||
"factory.ts": "^1.4.1",
|
"factory.ts": "^1.3.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.21",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.1.0",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^3.2.4",
|
||||||
"prettier-plugin-svelte": "^3.2.1",
|
"prettier-plugin-svelte": "^3.1.2",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"svelte": "^4.2.12",
|
"svelte": "^4.2.11",
|
||||||
"svelte-check": "^3.6.5",
|
"svelte-check": "^3.6.4",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.2.7",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.1.4",
|
"vite": "^5.1.1",
|
||||||
"vitest": "^1.3.1"
|
"vitest": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
@@ -898,9 +898,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "8.57.0",
|
"version": "8.56.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
|
||||||
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
|
"integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
@@ -948,13 +948,13 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.14",
|
"version": "0.11.13",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
|
||||||
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
|
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@humanwhocodes/object-schema": "^2.0.2",
|
"@humanwhocodes/object-schema": "^2.0.1",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.1.1",
|
||||||
"minimatch": "^3.0.5"
|
"minimatch": "^3.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -975,9 +975,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/object-schema": {
|
"node_modules/@humanwhocodes/object-schema": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
|
||||||
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
|
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@img/sharp-darwin-arm64": {
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
@@ -1859,9 +1859,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.1.tgz",
|
||||||
"integrity": "sha512-1Pm2lsBYURQsjnLyZa+jw75eVD4gYHxGRwPyFe4DAmB3FjTVR8vRNWGeuDLGFcKMh/B1ij6FTUrc9GrerogCng==",
|
"integrity": "sha512-TKj08o3mJCoQNLTdRdGkHPePTCPUGTgkew65RDqjVU3MtPVxljsofXQYfXndHfq0P7KoPRO/0/reF6HesU0Djw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2284,9 +2284,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.8",
|
"version": "7.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
|
||||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
"integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/supercluster": {
|
"node_modules/@types/supercluster": {
|
||||||
@@ -2298,16 +2298,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "7.1.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz",
|
||||||
"integrity": "sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==",
|
"integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.5.1",
|
"@eslint-community/regexpp": "^4.5.1",
|
||||||
"@typescript-eslint/scope-manager": "7.1.0",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/type-utils": "7.1.0",
|
"@typescript-eslint/type-utils": "7.0.1",
|
||||||
"@typescript-eslint/utils": "7.1.0",
|
"@typescript-eslint/utils": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.1.0",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.2.4",
|
"ignore": "^5.2.4",
|
||||||
@@ -2366,15 +2366,15 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "7.1.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz",
|
||||||
"integrity": "sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==",
|
"integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "7.1.0",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/types": "7.1.0",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/typescript-estree": "7.1.0",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.1.0",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2394,13 +2394,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "7.1.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz",
|
||||||
"integrity": "sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==",
|
"integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.1.0",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.1.0"
|
"@typescript-eslint/visitor-keys": "7.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^16.0.0 || >=18.0.0"
|
||||||
@@ -2411,13 +2411,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "7.1.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz",
|
||||||
"integrity": "sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==",
|
"integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "7.1.0",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"@typescript-eslint/utils": "7.1.0",
|
"@typescript-eslint/utils": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.0.1"
|
"ts-api-utils": "^1.0.1"
|
||||||
},
|
},
|
||||||
@@ -2438,9 +2438,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "7.1.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz",
|
||||||
"integrity": "sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==",
|
"integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || >=18.0.0"
|
"node": "^16.0.0 || >=18.0.0"
|
||||||
@@ -2451,13 +2451,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "7.1.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz",
|
||||||
"integrity": "sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==",
|
"integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.1.0",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/visitor-keys": "7.1.0",
|
"@typescript-eslint/visitor-keys": "7.0.1",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -2536,17 +2536,17 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "7.1.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz",
|
||||||
"integrity": "sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==",
|
"integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@types/json-schema": "^7.0.12",
|
"@types/json-schema": "^7.0.12",
|
||||||
"@types/semver": "^7.5.0",
|
"@types/semver": "^7.5.0",
|
||||||
"@typescript-eslint/scope-manager": "7.1.0",
|
"@typescript-eslint/scope-manager": "7.0.1",
|
||||||
"@typescript-eslint/types": "7.1.0",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"@typescript-eslint/typescript-estree": "7.1.0",
|
"@typescript-eslint/typescript-estree": "7.0.1",
|
||||||
"semver": "^7.5.4"
|
"semver": "^7.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2594,12 +2594,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "7.1.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz",
|
||||||
"integrity": "sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==",
|
"integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.1.0",
|
"@typescript-eslint/types": "7.0.1",
|
||||||
"eslint-visitor-keys": "^3.4.1"
|
"eslint-visitor-keys": "^3.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2617,23 +2617,24 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/browser": {
|
"node_modules/@vitest/browser": {
|
||||||
"version": "1.3.1",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-1.0.4.tgz",
|
||||||
"integrity": "sha512-pRof8G8nqRWwg3ouyIctyhfIVk5jXgF056uF//sqdi37+pVtDz9kBI/RMu0xlc8tgCyJ2aEMfbgJZPUydlEVaQ==",
|
"integrity": "sha512-qMT1NhClex73eA2sOwnlwLcSIVCW8B7NFVzIKuXLKxSJD3LsNq8PCKhwOkBxklbSAcZdkOgL/d3/gzQT7k9eng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "1.3.1",
|
"estree-walker": "^3.0.3",
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
"sirv": "^2.0.4"
|
"sirv": "^2.0.3"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"playwright": "*",
|
"playwright": "*",
|
||||||
"vitest": "1.3.1",
|
"safaridriver": "*",
|
||||||
|
"vitest": "^1.0.0",
|
||||||
"webdriverio": "*"
|
"webdriverio": "*"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
@@ -2649,9 +2650,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/coverage-v8": {
|
"node_modules/@vitest/coverage-v8": {
|
||||||
"version": "1.3.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.2.2.tgz",
|
||||||
"integrity": "sha512-UuBnkSJUNE9rdHjDCPyJ4fYuMkoMtnghes1XohYa4At0MS3OQSAo97FrbwSLRshYsXThMZy1+ybD/byK5llyIg==",
|
"integrity": "sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.1",
|
"@ampproject/remapping": "^2.2.1",
|
||||||
@@ -2672,17 +2673,17 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vitest": "1.3.1"
|
"vitest": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "1.3.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz",
|
||||||
"integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==",
|
"integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/spy": "1.3.1",
|
"@vitest/spy": "1.2.2",
|
||||||
"@vitest/utils": "1.3.1",
|
"@vitest/utils": "1.2.2",
|
||||||
"chai": "^4.3.10"
|
"chai": "^4.3.10"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -2690,12 +2691,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/runner": {
|
"node_modules/@vitest/runner": {
|
||||||
"version": "1.3.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz",
|
||||||
"integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==",
|
"integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "1.3.1",
|
"@vitest/utils": "1.2.2",
|
||||||
"p-limit": "^5.0.0",
|
"p-limit": "^5.0.0",
|
||||||
"pathe": "^1.1.1"
|
"pathe": "^1.1.1"
|
||||||
},
|
},
|
||||||
@@ -2731,9 +2732,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/snapshot": {
|
"node_modules/@vitest/snapshot": {
|
||||||
"version": "1.3.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz",
|
||||||
"integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==",
|
"integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
@@ -2777,9 +2778,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/spy": {
|
"node_modules/@vitest/spy": {
|
||||||
"version": "1.3.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz",
|
||||||
"integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==",
|
"integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tinyspy": "^2.2.0"
|
"tinyspy": "^2.2.0"
|
||||||
@@ -2789,9 +2790,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/utils": {
|
"node_modules/@vitest/utils": {
|
||||||
"version": "1.3.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz",
|
||||||
"integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==",
|
"integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-sequences": "^29.6.3",
|
"diff-sequences": "^29.6.3",
|
||||||
@@ -2836,9 +2837,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@zoom-image/core": {
|
"node_modules/@zoom-image/core": {
|
||||||
"version": "0.33.0",
|
"version": "0.32.1",
|
||||||
"resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.33.0.tgz",
|
"resolved": "https://registry.npmjs.org/@zoom-image/core/-/core-0.32.1.tgz",
|
||||||
"integrity": "sha512-wkMV8+aE7PeknLFhpIb/6vwRl09Z2gWM4UqKdnXO6Mb0pP9BiuDLcLvGGGB4o++uAPINgDwmNn+Loo641XSjDA==",
|
"integrity": "sha512-R56D749Ck+/1yLWlEJ2FctxjdpTQEje3jPhOAbeEZGzLndIumskO42UqRNixcER6sAzCi01oYopmqnCpDElF0g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@namnode/store": "^0.1.0"
|
"@namnode/store": "^0.1.0"
|
||||||
},
|
},
|
||||||
@@ -2848,11 +2849,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@zoom-image/svelte": {
|
"node_modules/@zoom-image/svelte": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@zoom-image/svelte/-/svelte-0.2.4.tgz",
|
||||||
"integrity": "sha512-dEpA/egmTjVcptwhtcKHvkhVMTzQCpH17erfcXuJByt+nn5Oo4LnZOxE8gwSVEdPp65Ns6Y/byYD0GSQ/vv+DQ==",
|
"integrity": "sha512-rgfgn7Q60VrwmE4MPBzDWaFplc+411Lxg1nMdAnq/UTv4HTWSpiwm1IOg8gQZjRp92a8RXcRmUYXU+wFKEMjSg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zoom-image/core": "0.33.0"
|
"@zoom-image/core": "0.32.1"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -4054,16 +4055,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "8.57.0",
|
"version": "8.56.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
|
||||||
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
|
"integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
"@eslint/eslintrc": "^2.1.4",
|
"@eslint/eslintrc": "^2.1.4",
|
||||||
"@eslint/js": "8.57.0",
|
"@eslint/js": "8.56.0",
|
||||||
"@humanwhocodes/config-array": "^0.11.14",
|
"@humanwhocodes/config-array": "^0.11.13",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
"@ungap/structured-clone": "^1.2.0",
|
"@ungap/structured-clone": "^1.2.0",
|
||||||
@@ -7990,23 +7991,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/strip-literal": {
|
"node_modules/strip-literal": {
|
||||||
"version": "2.0.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz",
|
||||||
"integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==",
|
"integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-tokens": "^8.0.2"
|
"acorn": "^8.10.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/strip-literal/node_modules/js-tokens": {
|
|
||||||
"version": "8.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz",
|
|
||||||
"integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/sucrase": {
|
"node_modules/sucrase": {
|
||||||
"version": "3.34.0",
|
"version": "3.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
|
||||||
@@ -8082,9 +8077,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte": {
|
"node_modules/svelte": {
|
||||||
"version": "4.2.12",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.12.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.11.tgz",
|
||||||
"integrity": "sha512-d8+wsh5TfPwqVzbm4/HCXC783/KPHV60NvwitJnyTA5lWn1elhXMNWhXGCJ7PwPa8qFUnyJNIyuIRt2mT0WMug==",
|
"integrity": "sha512-YIQk3J4X89wOLhjsqIW8tqY3JHPuBdtdOIkASP2PZeAMcSW9RsIjQzMesCrxOF3gdWYC0mKknlKF7OqmLM+Zqg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.1",
|
"@ampproject/remapping": "^2.2.1",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15",
|
"@jridgewell/sourcemap-codec": "^1.4.15",
|
||||||
@@ -8106,9 +8101,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte-check": {
|
"node_modules/svelte-check": {
|
||||||
"version": "3.6.5",
|
"version": "3.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.4.tgz",
|
||||||
"integrity": "sha512-5aLgoQEdadvp8ypvKQ2avhnQ+V9YPQQaWrTFlXFw5g/v8xIQBvo+X/WqxTyD+V/ItDqXg3+abUA53rdDHgUjCA==",
|
"integrity": "sha512-mY/dqucqm46p72M8yZmn81WPZx9mN6uuw8UVfR3ZKQeLxQg5HDGO3HHm5AZuWZPYNMLJ+TRMn+TeN53HfQ/vsw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/trace-mapping": "^0.3.17",
|
"@jridgewell/trace-mapping": "^0.3.17",
|
||||||
@@ -8178,9 +8173,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte-maplibre": {
|
"node_modules/svelte-maplibre": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-maplibre/-/svelte-maplibre-0.8.0.tgz",
|
||||||
"integrity": "sha512-CTm/s0+mJzBHSoO5zPKBo3ORmUyiWS3Ex4xvVdNgVg+sDesHasEAJ0N1/NUrd56S33zgRdFZGzRnRguCnKFAzw==",
|
"integrity": "sha512-sRSj/zQa7LTfHNIzKcYe+sa9qHClt/OAXcdPQ0w3ksLbCMmVHGk4B2yIXHCVk0g4sc18M85N8KGsHVtZoNC+Mw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"d3-geo": "^3.1.0",
|
"d3-geo": "^3.1.0",
|
||||||
"just-compare": "^2.3.0",
|
"just-compare": "^2.3.0",
|
||||||
@@ -8461,9 +8456,9 @@
|
|||||||
"integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA=="
|
"integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA=="
|
||||||
},
|
},
|
||||||
"node_modules/tinyspy": {
|
"node_modules/tinyspy": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz",
|
||||||
"integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
|
"integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@@ -8738,9 +8733,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.1.4",
|
"version": "5.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz",
|
||||||
"integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==",
|
"integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.19.3",
|
||||||
@@ -8806,9 +8801,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-node": {
|
"node_modules/vite-node": {
|
||||||
"version": "1.3.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz",
|
||||||
"integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==",
|
"integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
@@ -8842,17 +8837,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
"version": "1.3.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz",
|
||||||
"integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==",
|
"integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "1.3.1",
|
"@vitest/expect": "1.2.2",
|
||||||
"@vitest/runner": "1.3.1",
|
"@vitest/runner": "1.2.2",
|
||||||
"@vitest/snapshot": "1.3.1",
|
"@vitest/snapshot": "1.2.2",
|
||||||
"@vitest/spy": "1.3.1",
|
"@vitest/spy": "1.2.2",
|
||||||
"@vitest/utils": "1.3.1",
|
"@vitest/utils": "1.2.2",
|
||||||
"acorn-walk": "^8.3.2",
|
"acorn-walk": "^8.3.2",
|
||||||
|
"cac": "^6.7.14",
|
||||||
"chai": "^4.3.10",
|
"chai": "^4.3.10",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
@@ -8861,11 +8857,11 @@
|
|||||||
"pathe": "^1.1.1",
|
"pathe": "^1.1.1",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
"std-env": "^3.5.0",
|
"std-env": "^3.5.0",
|
||||||
"strip-literal": "^2.0.0",
|
"strip-literal": "^1.3.0",
|
||||||
"tinybench": "^2.5.1",
|
"tinybench": "^2.5.1",
|
||||||
"tinypool": "^0.8.2",
|
"tinypool": "^0.8.2",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vite-node": "1.3.1",
|
"vite-node": "1.2.2",
|
||||||
"why-is-node-running": "^2.2.2"
|
"why-is-node-running": "^2.2.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -8880,8 +8876,8 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@edge-runtime/vm": "*",
|
"@edge-runtime/vm": "*",
|
||||||
"@types/node": "^18.0.0 || >=20.0.0",
|
"@types/node": "^18.0.0 || >=20.0.0",
|
||||||
"@vitest/browser": "1.3.1",
|
"@vitest/browser": "^1.0.0",
|
||||||
"@vitest/ui": "1.3.1",
|
"@vitest/ui": "^1.0.0",
|
||||||
"happy-dom": "*",
|
"happy-dom": "*",
|
||||||
"jsdom": "*"
|
"jsdom": "*"
|
||||||
},
|
},
|
||||||
|
|||||||
+35
-35
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.2.0",
|
"version": "1.1.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
||||||
@@ -22,59 +22,59 @@
|
|||||||
"prepare": "svelte-kit sync"
|
"prepare": "svelte-kit sync"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faker-js/faker": "^8.4.1",
|
"@faker-js/faker": "^8.0.0",
|
||||||
"@floating-ui/dom": "^1.6.3",
|
"@floating-ui/dom": "^1.5.1",
|
||||||
"@socket.io/component-emitter": "^3.1.0",
|
"@socket.io/component-emitter": "^3.1.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/enhanced-img": "^0.1.8",
|
"@sveltejs/enhanced-img": "^0.1.8",
|
||||||
"@sveltejs/kit": "^2.5.2",
|
"@sveltejs/kit": "^2.5.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||||
"@testing-library/jest-dom": "^6.4.2",
|
"@testing-library/jest-dom": "^6.1.5",
|
||||||
"@testing-library/svelte": "^4.1.0",
|
"@testing-library/svelte": "^4.0.3",
|
||||||
"@types/dom-to-image": "^2.6.7",
|
"@types/dom-to-image": "^2.6.4",
|
||||||
"@types/justified-layout": "^4.1.4",
|
"@types/justified-layout": "^4.1.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.6",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.2.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^7.1.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"@vitest/coverage-v8": "^1.3.1",
|
"@vitest/coverage-v8": "^1.0.4",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.13",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.34.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.35.1",
|
"eslint-plugin-svelte": "^2.30.0",
|
||||||
"eslint-plugin-unicorn": "^51.0.1",
|
"eslint-plugin-unicorn": "^51.0.0",
|
||||||
"factory.ts": "^1.4.1",
|
"factory.ts": "^1.3.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.21",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.1.0",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^3.2.4",
|
||||||
"prettier-plugin-svelte": "^3.2.1",
|
"prettier-plugin-svelte": "^3.1.2",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"svelte": "^4.2.12",
|
"svelte": "^4.2.11",
|
||||||
"svelte-check": "^3.6.5",
|
"svelte-check": "^3.6.4",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.2.7",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.1.4",
|
"vite": "^5.1.1",
|
||||||
"vitest": "^1.3.1"
|
"vitest": "^1.0.4"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.3.67",
|
||||||
"@photo-sphere-viewer/core": "^5.7.1",
|
"@photo-sphere-viewer/core": "^5.7.0",
|
||||||
"@zoom-image/svelte": "^0.2.6",
|
"@zoom-image/svelte": "^0.2.0",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"copy-image-clipboard": "^2.1.2",
|
"copy-image-clipboard": "^2.1.2",
|
||||||
"dom-to-image": "^2.6.0",
|
"dom-to-image": "^2.6.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.7",
|
||||||
"justified-layout": "^4.1.0",
|
"justified-layout": "^4.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.2.1",
|
||||||
"socket.io-client": "^4.7.4",
|
"socket.io-client": "^4.6.1",
|
||||||
"svelte-local-storage-store": "^0.6.4",
|
"svelte-local-storage-store": "^0.6.0",
|
||||||
"svelte-maplibre": "^0.8.1",
|
"svelte-maplibre": "^0.8.0",
|
||||||
"thumbhash": "^0.1.1"
|
"thumbhash": "^0.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { isHttpError } from '@immich/sdk';
|
import { isHttpError } from '@immich/sdk';
|
||||||
import type { HandleClientError } from '@sveltejs/kit';
|
import type { HandleClientError } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
const LOG_PREFIX = '[hooks.client.ts]';
|
||||||
const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?';
|
const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?';
|
||||||
|
|
||||||
const parseError = (error: unknown) => {
|
const parseError = (error: unknown) => {
|
||||||
@@ -22,6 +23,6 @@ const parseError = (error: unknown) => {
|
|||||||
|
|
||||||
export const handleError: HandleClientError = ({ error }) => {
|
export const handleError: HandleClientError = ({ error }) => {
|
||||||
const result = parseError(error);
|
const result = parseError(error);
|
||||||
console.error(`[hooks.client.ts]:handleError ${result.message}`);
|
console.error(`${LOG_PREFIX}:handleError ${result.message}`);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,11 +48,11 @@
|
|||||||
await handleCommand(jobId, dto);
|
await handleCommand(jobId, dto);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onConfirm = async () => {
|
const onConfirm = () => {
|
||||||
if (!confirmJob) {
|
if (!confirmJob) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await handleCommand(confirmJob, { command: JobCommand.Start, force: true });
|
handleCommand(confirmJob, { command: JobCommand.Start, force: true });
|
||||||
confirmJob = null;
|
confirmJob = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetToDefault = (configKeys: Array<keyof SystemConfigDto>) => {
|
const resetToDefault = async (configKeys: Array<keyof SystemConfigDto>) => {
|
||||||
for (const key of configKeys) {
|
for (const key of configKeys) {
|
||||||
config = { ...config, [key]: defaultConfig[key] };
|
config = { ...config, [key]: defaultConfig[key] };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js';
|
import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js';
|
||||||
import UpdatePanel from '../shared-components/update-panel.svelte';
|
import UpdatePanel from '../shared-components/update-panel.svelte';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
|
|
||||||
export let sharedLink: SharedLinkResponseDto;
|
export let sharedLink: SharedLinkResponseDto;
|
||||||
export let user: UserResponseDto | undefined = undefined;
|
export let user: UserResponseDto | undefined = undefined;
|
||||||
@@ -36,7 +35,7 @@
|
|||||||
|
|
||||||
dragAndDropFilesStore.subscribe((value) => {
|
dragAndDropFilesStore.subscribe((value) => {
|
||||||
if (value.isDragging && value.files.length > 0) {
|
if (value.isDragging && value.files.length > 0) {
|
||||||
handlePromiseError(fileUploadHandler(value.files, album.id));
|
fileUploadHandler(value.files, album.id);
|
||||||
dragAndDropFilesStore.set({ isDragging: false, files: [] });
|
dragAndDropFilesStore.set({ isDragging: false, files: [] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -68,7 +67,7 @@
|
|||||||
|
|
||||||
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
|
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
document.addEventListener('keydown', onKeyboardPress);
|
document.addEventListener('keydown', onKeyboardPress);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { getAssetType } from '$lib/utils/asset-utils';
|
import { getAssetType } from '$lib/utils/asset-utils';
|
||||||
import { autoGrowHeight } from '$lib/utils/autogrow';
|
import { autoGrowHeight } from '$lib/utils/autogrow';
|
||||||
import { clickOutside } from '$lib/utils/click-outside';
|
import { clickOutside } from '$lib/utils/click-outside';
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (assetId && previousAssetId != assetId) {
|
if (assetId && previousAssetId != assetId) {
|
||||||
handlePromiseError(getReactions());
|
getReactions();
|
||||||
previousAssetId = assetId;
|
previousAssetId = assetId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,10 +95,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnter = async (event: KeyboardEvent) => {
|
const handleEnter = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
await handleSendComment();
|
handleSendComment();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
|
import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { getAssetJobMessage, isSharedLink, handlePromiseError } from '$lib/utils';
|
import { getAssetJobMessage, isSharedLink } from '$lib/utils';
|
||||||
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
|
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
@@ -174,8 +174,8 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (isShared && asset.id) {
|
if (isShared && asset.id) {
|
||||||
handlePromiseError(getFavorite());
|
getFavorite();
|
||||||
handlePromiseError(getNumberOfComments());
|
getNumberOfComments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,9 +184,9 @@
|
|||||||
if (value === SlideshowState.PlaySlideshow) {
|
if (value === SlideshowState.PlaySlideshow) {
|
||||||
slideshowHistory.reset();
|
slideshowHistory.reset();
|
||||||
slideshowHistory.queue(asset.id);
|
slideshowHistory.queue(asset.id);
|
||||||
handlePromiseError(handlePlaySlideshow());
|
handlePlaySlideshow();
|
||||||
} else if (value === SlideshowState.StopSlideshow) {
|
} else if (value === SlideshowState.StopSlideshow) {
|
||||||
handlePromiseError(handleStopSlideshow());
|
handleStopSlideshow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$: asset.id && !sharedLink && handlePromiseError(handleGetAllAlbums()); // Update the album information when the asset ID changes
|
$: asset.id && !sharedLink && handleGetAllAlbums(); // Update the album information when the asset ID changes
|
||||||
|
|
||||||
const handleGetAllAlbums = async () => {
|
const handleGetAllAlbums = async () => {
|
||||||
if (isSharedLink()) {
|
if (isSharedLink()) {
|
||||||
@@ -247,7 +247,7 @@
|
|||||||
isShowActivity = !isShowActivity;
|
isShowActivity = !isShowActivity;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeypress = async (event: KeyboardEvent) => {
|
const handleKeypress = (event: KeyboardEvent) => {
|
||||||
if (shouldIgnoreShortcut(event)) {
|
if (shouldIgnoreShortcut(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@
|
|||||||
case 'a':
|
case 'a':
|
||||||
case 'A': {
|
case 'A': {
|
||||||
if (shiftKey) {
|
if (shiftKey) {
|
||||||
await toggleArchive();
|
toggleArchive();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -273,18 +273,18 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'ArrowRight': {
|
case 'ArrowRight': {
|
||||||
await navigateAssetForward();
|
navigateAssetForward();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'd':
|
case 'd':
|
||||||
case 'D': {
|
case 'D': {
|
||||||
if (shiftKey) {
|
if (shiftKey) {
|
||||||
await downloadFile(asset);
|
downloadFile(asset);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'Delete': {
|
case 'Delete': {
|
||||||
await trashOrDelete(shiftKey);
|
trashOrDelete(shiftKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'Escape': {
|
case 'Escape': {
|
||||||
@@ -296,7 +296,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'f': {
|
case 'f': {
|
||||||
await toggleFavorite();
|
toggleFavorite();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'i': {
|
case 'i': {
|
||||||
@@ -326,7 +326,7 @@
|
|||||||
|
|
||||||
slideshowHistory.queue(asset.id);
|
slideshowHistory.queue(asset.id);
|
||||||
|
|
||||||
await setAssetId(asset.id);
|
setAssetId(asset.id);
|
||||||
$restartSlideshowProgress = true;
|
$restartSlideshowProgress = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -369,17 +369,17 @@
|
|||||||
$isShowDetail = !$isShowDetail;
|
$isShowDetail = !$isShowDetail;
|
||||||
};
|
};
|
||||||
|
|
||||||
const trashOrDelete = async (force: boolean = false) => {
|
const trashOrDelete = (force: boolean = false) => {
|
||||||
if (force || !isTrashEnabled) {
|
if (force || !isTrashEnabled) {
|
||||||
if ($showDeleteModal) {
|
if ($showDeleteModal) {
|
||||||
isShowDeleteConfirmation = true;
|
isShowDeleteConfirmation = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await deleteAsset();
|
deleteAsset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await trashAsset();
|
trashAsset();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -432,7 +432,7 @@
|
|||||||
message: asset.isFavorite ? `Added to favorites` : `Removed from favorites`,
|
message: asset.isFavorite ? `Added to favorites` : `Removed from favorites`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, `Unable to ${asset.isFavorite ? `add asset to` : `remove asset from`} favorites`);
|
await handleError(error, `Unable to ${asset.isFavorite ? `add asset to` : `remove asset from`} favorites`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -472,7 +472,7 @@
|
|||||||
message: asset.isArchived ? `Added to archive` : `Removed from archive`,
|
message: asset.isArchived ? `Added to archive` : `Removed from archive`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, `Unable to ${asset.isArchived ? `add asset to` : `remove asset from`} archive`);
|
await handleError(error, `Unable to ${asset.isArchived ? `add asset to` : `remove asset from`} archive`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -481,7 +481,7 @@
|
|||||||
await runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } });
|
await runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } });
|
||||||
notificationController.show({ type: NotificationType.Info, message: getAssetJobMessage(name) });
|
notificationController.show({ type: NotificationType.Info, message: getAssetJobMessage(name) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, `Unable to submit job`);
|
await handleError(error, `Unable to submit job`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -492,7 +492,7 @@
|
|||||||
let assetViewerHtmlElement: HTMLElement;
|
let assetViewerHtmlElement: HTMLElement;
|
||||||
|
|
||||||
const slideshowHistory = new SlideshowHistory((assetId: string) => {
|
const slideshowHistory = new SlideshowHistory((assetId: string) => {
|
||||||
handlePromiseError(setAssetId(assetId));
|
setAssetId(assetId);
|
||||||
$restartSlideshowProgress = true;
|
$restartSlideshowProgress = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -550,7 +550,7 @@
|
|||||||
dispatch('close');
|
dispatch('close');
|
||||||
notificationController.show({ type: NotificationType.Info, message: 'Un-stacked', timeout: 1500 });
|
notificationController.show({ type: NotificationType.Info, message: 'Un-stacked', timeout: 1500 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, `Unable to unstack`);
|
await handleError(error, `Unable to unstack`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { websocketEvents } from '$lib/stores/websocket';
|
import { websocketEvents } from '$lib/stores/websocket';
|
||||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl, isSharedLink, handlePromiseError } from '$lib/utils';
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||||
import { delay, getAssetFilename } from '$lib/utils/asset-utils';
|
import { delay, getAssetFilename } from '$lib/utils/asset-utils';
|
||||||
import { autoGrowHeight } from '$lib/utils/autogrow';
|
import { autoGrowHeight } from '$lib/utils/autogrow';
|
||||||
import { clickOutside } from '$lib/utils/click-outside';
|
import { clickOutside } from '$lib/utils/click-outside';
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
originalDescription = description;
|
originalDescription = description;
|
||||||
};
|
};
|
||||||
|
|
||||||
$: handlePromiseError(handleNewAsset(asset));
|
$: handleNewAsset(asset);
|
||||||
|
|
||||||
$: latlng = (() => {
|
$: latlng = (() => {
|
||||||
const lat = asset.exifInfo?.latitude;
|
const lat = asset.exifInfo?.latitude;
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Enter': {
|
case 'Enter': {
|
||||||
if (ctrl && event.target === textArea) {
|
if (ctrl && event.target === textArea) {
|
||||||
await handleFocusOut();
|
handleFocusOut();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
||||||
import { getKey, handlePromiseError } from '$lib/utils';
|
import { getKey } from '$lib/utils';
|
||||||
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||||
import { getBoundingBox } from '$lib/utils/people-utils';
|
import { getBoundingBox } from '$lib/utils/people-utils';
|
||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const doZoomImage = () => {
|
const doZoomImage = async () => {
|
||||||
setZoomImageWheelState({
|
setZoomImageWheelState({
|
||||||
currentZoom: $zoomImageWheelState.currentZoom === 1 ? 2 : 1,
|
currentZoom: $zoomImageWheelState.currentZoom === 1 ? 2 : 1,
|
||||||
});
|
});
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
if (state.currentZoom > 1 && isWebCompatibleImage(asset) && !hasZoomed && !$alwaysLoadOriginalFile) {
|
if (state.currentZoom > 1 && isWebCompatibleImage(asset) && !hasZoomed && !$alwaysLoadOriginalFile) {
|
||||||
hasZoomed = true;
|
hasZoomed = true;
|
||||||
|
|
||||||
handlePromiseError(loadAssetData({ loadOriginal: true }));
|
loadAssetData({ loadOriginal: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
video.muted = false;
|
video.muted = false;
|
||||||
dispatch('onVideoStarted');
|
dispatch('onVideoStarted');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to play video');
|
await handleError(error, 'Unable to play video');
|
||||||
} finally {
|
} finally {
|
||||||
isVideoLoading = false;
|
isVideoLoading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
if (assetType === AssetTypeEnum.Image) {
|
if (assetType === AssetTypeEnum.Image) {
|
||||||
image = $photoViewer;
|
image = $photoViewer;
|
||||||
} else if (assetType === AssetTypeEnum.Video) {
|
} else if (assetType === AssetTypeEnum.Video) {
|
||||||
const data = getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp);
|
const data = await getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp);
|
||||||
const img: HTMLImageElement = new Image();
|
const img: HTMLImageElement = new Image();
|
||||||
img.src = data;
|
img.src = data;
|
||||||
|
|
||||||
|
|||||||
@@ -43,10 +43,10 @@
|
|||||||
dispatch('back');
|
dispatch('back');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSwapPeople = async () => {
|
const handleSwapPeople = () => {
|
||||||
[person, selectedPeople[0]] = [selectedPeople[0], person];
|
[person, selectedPeople[0]] = [selectedPeople[0], person];
|
||||||
$page.url.searchParams.set(QueryParameter.ACTION, ActionQueryParameterValue.MERGE);
|
$page.url.searchParams.set(QueryParameter.ACTION, ActionQueryParameterValue.MERGE);
|
||||||
await goto(`${AppRoute.PEOPLE}/${person.id}?${$page.url.searchParams.toString()}`);
|
goto(`${AppRoute.PEOPLE}/${person.id}?${$page.url.searchParams.toString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (selected: PersonResponseDto) => {
|
const onSelect = (selected: PersonResponseDto) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { websocketEvents } from '$lib/stores/websocket';
|
import { websocketEvents } from '$lib/stores/websocket';
|
||||||
import { getPeopleThumbnailUrl, handlePromiseError } from '$lib/utils';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getPersonNameWithHiddenValue } from '$lib/utils/person';
|
import { getPersonNameWithHiddenValue } from '$lib/utils/person';
|
||||||
import {
|
import {
|
||||||
@@ -46,8 +46,8 @@
|
|||||||
let allPeople: PersonResponseDto[] = [];
|
let allPeople: PersonResponseDto[] = [];
|
||||||
|
|
||||||
// timers
|
// timers
|
||||||
let loaderLoadingDoneTimeout: ReturnType<typeof setTimeout>;
|
let loaderLoadingDoneTimeout: NodeJS.Timeout;
|
||||||
let automaticRefreshTimeout: ReturnType<typeof setTimeout>;
|
let automaticRefreshTimeout: NodeJS.Timeout;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
close: void;
|
close: void;
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
handlePromiseError(loadPeople());
|
loadPeople();
|
||||||
return websocketEvents.on('on_person_thumbnail', onPersonThumbnail);
|
return websocketEvents.on('on_person_thumbnail', onPersonThumbnail);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePersonPicker = (index: number) => {
|
const handlePersonPicker = async (index: number) => {
|
||||||
editedPersonIndex = index;
|
editedPersonIndex = index;
|
||||||
showSeletecFaces = true;
|
showSeletecFaces = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -132,7 +132,9 @@
|
|||||||
title={'Assign selected assets to a new person'}
|
title={'Assign selected assets to a new person'}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
disabled={disableButtons || hasSelection}
|
disabled={disableButtons || hasSelection}
|
||||||
on:click={handleCreate}
|
on:click={() => {
|
||||||
|
handleCreate();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{#if !showLoadingSpinnerCreate}
|
{#if !showLoadingSpinnerCreate}
|
||||||
<Icon path={mdiPlus} size={18} />
|
<Icon path={mdiPlus} size={18} />
|
||||||
@@ -145,7 +147,9 @@
|
|||||||
size={'sm'}
|
size={'sm'}
|
||||||
title={'Assign selected assets to an existing person'}
|
title={'Assign selected assets to an existing person'}
|
||||||
disabled={disableButtons || !hasSelection}
|
disabled={disableButtons || !hasSelection}
|
||||||
on:click={handleReassign}
|
on:click={() => {
|
||||||
|
handleReassign();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{#if !showLoadingSpinnerReassign}
|
{#if !showLoadingSpinnerReassign}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
dispatch('submit', { library, type: LibraryType.External });
|
dispatch('submit', { library, type: LibraryType.External });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddExclusionPattern = () => {
|
const handleAddExclusionPattern = async () => {
|
||||||
if (!addExclusionPattern) {
|
if (!addExclusionPattern) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditExclusionPattern = () => {
|
const handleEditExclusionPattern = async () => {
|
||||||
if (editExclusionPattern === null) {
|
if (editExclusionPattern === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteExclusionPattern = () => {
|
const handleDeleteExclusionPattern = async () => {
|
||||||
if (editExclusionPattern === null) {
|
if (editExclusionPattern === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to connect!');
|
await handleError(error, 'Unable to connect!');
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthLoading = false;
|
oauthLoading = false;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||||
import type { Viewport } from '$lib/stores/assets.store';
|
import type { Viewport } from '$lib/stores/assets.store';
|
||||||
import { memoryStore } from '$lib/stores/memory.store';
|
import { memoryStore } from '$lib/stores/memory.store';
|
||||||
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { fromLocalDateTime } from '$lib/utils/timeline-util';
|
import { fromLocalDateTime } from '$lib/utils/timeline-util';
|
||||||
import { ThumbnailFormat, getMemoryLane } from '@immich/sdk';
|
import { ThumbnailFormat, getMemoryLane } from '@immich/sdk';
|
||||||
import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiPause, mdiPlay } from '@mdi/js';
|
import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiPause, mdiPlay } from '@mdi/js';
|
||||||
@@ -59,30 +59,30 @@
|
|||||||
let paused = false;
|
let paused = false;
|
||||||
|
|
||||||
// Play or pause progress when the paused state changes.
|
// Play or pause progress when the paused state changes.
|
||||||
$: paused ? handlePromiseError(pause()) : handlePromiseError(play());
|
$: paused ? pause() : play();
|
||||||
|
|
||||||
// Progress should be paused when it's no longer possible to advance.
|
// Progress should be paused when it's no longer possible to advance.
|
||||||
$: paused ||= !canGoForward || galleryInView;
|
$: paused ||= !canGoForward || galleryInView;
|
||||||
|
|
||||||
// Advance to the next asset or memory when progress is complete.
|
// Advance to the next asset or memory when progress is complete.
|
||||||
$: $progress === 1 && handlePromiseError(toNext());
|
$: $progress === 1 && toNext();
|
||||||
|
|
||||||
// Progress should be resumed when reset and not paused.
|
// Progress should be resumed when reset and not paused.
|
||||||
$: !$progress && !paused && handlePromiseError(play());
|
$: !$progress && !paused && play();
|
||||||
|
|
||||||
// Progress should be reset when the current memory or asset changes.
|
// Progress should be reset when the current memory or asset changes.
|
||||||
$: memoryIndex, assetIndex, handlePromiseError(reset());
|
$: memoryIndex, assetIndex, reset();
|
||||||
|
|
||||||
const handleKeyDown = async (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'ArrowRight' && canGoForward) {
|
if (e.key === 'ArrowRight' && canGoForward) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await toNext();
|
toNext();
|
||||||
} else if (e.key === 'ArrowLeft' && canGoBack) {
|
} else if (e.key === 'ArrowLeft' && canGoBack) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await toPrevious();
|
toPrevious();
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await goto(AppRoute.PHOTOS);
|
goto(AppRoute.PHOTOS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,22 +27,18 @@
|
|||||||
showAlbumPicker = false;
|
showAlbumPicker = false;
|
||||||
|
|
||||||
const assetIds = [...getAssets()].map((asset) => asset.id);
|
const assetIds = [...getAssets()].map((asset) => asset.id);
|
||||||
createAlbum({ createAlbumDto: { albumName, assetIds } })
|
createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => {
|
||||||
.then(async (response) => {
|
const { id, albumName } = response;
|
||||||
const { id, albumName } = response;
|
|
||||||
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
message: `Added ${assetIds.length} to ${albumName}`,
|
message: `Added ${assetIds.length} to ${albumName}`,
|
||||||
type: NotificationType.Info,
|
type: NotificationType.Info,
|
||||||
});
|
|
||||||
|
|
||||||
clearSelect();
|
|
||||||
|
|
||||||
await goto(`${AppRoute.ALBUMS}/${id}`);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(`[add-to-album.svelte]:handleAddToNewAlbum ${error}`, error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearSelect();
|
||||||
|
|
||||||
|
goto(`${AppRoute.ALBUMS}/${id}`);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddToAlbum = async (album: AlbumResponseDto) => {
|
const handleAddToAlbum = async (album: AlbumResponseDto) => {
|
||||||
|
|||||||
@@ -80,17 +80,13 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const assetClickHandler = async (
|
const assetClickHandler = (asset: AssetResponseDto, assetsInDateGroup: AssetResponseDto[], groupTitle: string) => {
|
||||||
asset: AssetResponseDto,
|
|
||||||
assetsInDateGroup: AssetResponseDto[],
|
|
||||||
groupTitle: string,
|
|
||||||
) => {
|
|
||||||
if (isSelectionMode || $isMultiSelectState) {
|
if (isSelectionMode || $isMultiSelectState) {
|
||||||
assetSelectHandler(asset, assetsInDateGroup, groupTitle);
|
assetSelectHandler(asset, assetsInDateGroup, groupTitle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await assetViewingStore.setAssetId(asset.id);
|
assetViewingStore.setAssetId(asset.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets });
|
const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets });
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
|
import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
|
||||||
import AssetDateGroup from './asset-date-group.svelte';
|
import AssetDateGroup from './asset-date-group.svelte';
|
||||||
import DeleteAssetDialog from './delete-asset-dialog.svelte';
|
import DeleteAssetDialog from './delete-asset-dialog.svelte';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
|
|
||||||
export let isSelectionMode = false;
|
export let isSelectionMode = false;
|
||||||
export let singleSelect = false;
|
export let singleSelect = false;
|
||||||
@@ -48,19 +47,19 @@
|
|||||||
$: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0;
|
$: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0;
|
||||||
$: idsSelectedAssets = [...$selectedAssets].filter((a) => !a.isExternal).map((a) => a.id);
|
$: idsSelectedAssets = [...$selectedAssets].filter((a) => !a.isExternal).map((a) => a.id);
|
||||||
|
|
||||||
|
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
|
||||||
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
|
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
|
||||||
|
|
||||||
const onKeydown = (event: KeyboardEvent) => handlePromiseError(handleKeyboardPress(event));
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
showSkeleton = false;
|
showSkeleton = false;
|
||||||
document.addEventListener('keydown', onKeydown);
|
document.addEventListener('keydown', onKeyboardPress);
|
||||||
assetStore.connect();
|
assetStore.connect();
|
||||||
await assetStore.init(viewport);
|
await assetStore.init(viewport);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
document.removeEventListener('keydown', onKeydown);
|
document.removeEventListener('keydown', onKeyboardPress);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($showAssetViewer) {
|
if ($showAssetViewer) {
|
||||||
@@ -70,13 +69,13 @@
|
|||||||
assetStore.disconnect();
|
assetStore.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
const trashOrDelete = async (force: boolean = false) => {
|
const trashOrDelete = (force: boolean = false) => {
|
||||||
isShowDeleteConfirmation = false;
|
isShowDeleteConfirmation = false;
|
||||||
await deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets);
|
deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets);
|
||||||
assetInteractionStore.clearMultiselect();
|
assetInteractionStore.clearMultiselect();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyboardPress = async (event: KeyboardEvent) => {
|
const handleKeyboardPress = (event: KeyboardEvent) => {
|
||||||
if ($isSearchEnabled || shouldIgnoreShortcut(event)) {
|
if ($isSearchEnabled || shouldIgnoreShortcut(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -99,7 +98,7 @@
|
|||||||
}
|
}
|
||||||
case '/': {
|
case '/': {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
await goto(AppRoute.EXPLORE);
|
goto(AppRoute.EXPLORE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'Delete': {
|
case 'Delete': {
|
||||||
@@ -113,7 +112,7 @@
|
|||||||
force = true;
|
force = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await trashOrDelete(force);
|
trashOrDelete(force);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -127,12 +126,12 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function intersectedHandler(event: CustomEvent) {
|
function intersectedHandler(event: CustomEvent) {
|
||||||
const element_ = event.detail.container as HTMLElement;
|
const element_ = event.detail.container as HTMLElement;
|
||||||
const target = element_.firstChild as HTMLElement;
|
const target = element_.firstChild as HTMLElement;
|
||||||
if (target) {
|
if (target) {
|
||||||
const bucketDate = target.id.split('_')[1];
|
const bucketDate = target.id.split('_')[1];
|
||||||
await assetStore.loadBucket(bucketDate, event.detail.position);
|
assetStore.loadBucket(bucketDate, event.detail.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +142,7 @@
|
|||||||
const handlePrevious = async () => {
|
const handlePrevious = async () => {
|
||||||
const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
|
const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
|
||||||
if (previousAsset) {
|
if (previousAsset) {
|
||||||
await assetViewingStore.setAssetId(previousAsset);
|
assetViewingStore.setAssetId(previousAsset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!previousAsset;
|
return !!previousAsset;
|
||||||
@@ -152,7 +151,7 @@
|
|||||||
const handleNext = async () => {
|
const handleNext = async () => {
|
||||||
const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
|
const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
|
||||||
if (nextAsset) {
|
if (nextAsset) {
|
||||||
await assetViewingStore.setAssetId(nextAsset);
|
assetViewingStore.setAssetId(nextAsset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!nextAsset;
|
return !!nextAsset;
|
||||||
@@ -370,7 +369,7 @@
|
|||||||
<DeleteAssetDialog
|
<DeleteAssetDialog
|
||||||
size={idsSelectedAssets.length}
|
size={idsSelectedAssets.length}
|
||||||
on:cancel={() => (isShowDeleteConfirmation = false)}
|
on:cancel={() => (isShowDeleteConfirmation = false)}
|
||||||
on:confirm={() => handlePromiseError(trashOrDelete(true))}
|
on:confirm={() => trashOrDelete(true)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
||||||
import { getKey, handlePromiseError } from '$lib/utils';
|
import { getKey } from '$lib/utils';
|
||||||
import { downloadArchive } from '$lib/utils/asset-utils';
|
import { downloadArchive } from '$lib/utils/asset-utils';
|
||||||
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
dragAndDropFilesStore.subscribe((value) => {
|
dragAndDropFilesStore.subscribe((value) => {
|
||||||
if (value.isDragging && value.files.length > 0) {
|
if (value.isDragging && value.files.length > 0) {
|
||||||
handlePromiseError(handleUploadAssets(value.files));
|
handleUploadAssets(value.files);
|
||||||
dragAndDropFilesStore.set({ isDragging: false, files: [] });
|
dragAndDropFilesStore.set({ isDragging: false, files: [] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
type: NotificationType.Info,
|
type: NotificationType.Info,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to add assets to shared link');
|
await handleError(error, 'Unable to add assets to shared link');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { Theme } from '$lib/constants';
|
import { Theme } from '$lib/constants';
|
||||||
import { colorTheme, mapSettings } from '$lib/stores/preferences.store';
|
import { colorTheme, mapSettings } from '$lib/stores/preferences.store';
|
||||||
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { getMapStyle, MapTheme, type MapMarkerResponseDto } from '@immich/sdk';
|
import { getMapStyle, MapTheme, type MapMarkerResponseDto } from '@immich/sdk';
|
||||||
import { mdiCog, mdiMapMarker } from '@mdi/js';
|
import { mdiCog, mdiMapMarker } from '@mdi/js';
|
||||||
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
|
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
|
||||||
@@ -152,7 +152,9 @@
|
|||||||
applyToClusters
|
applyToClusters
|
||||||
asButton
|
asButton
|
||||||
let:feature
|
let:feature
|
||||||
on:click={(event) => handlePromiseError(handleClusterClick(event.detail.feature.properties.cluster_id, map))}
|
on:click={(event) => {
|
||||||
|
handleClusterClick(event.detail.feature.properties.cluster_id, map);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="rounded-full w-[40px] h-[40px] bg-immich-primary text-immich-gray flex justify-center items-center font-mono font-bold shadow-lg hover:bg-immich-dark-primary transition-all duration-200 hover:text-immich-dark-bg opacity-90"
|
class="rounded-full w-[40px] h-[40px] bg-immich-primary text-immich-gray flex justify-center items-center font-mono font-bold shadow-lg hover:bg-immich-dark-primary transition-all duration-200 hover:text-immich-dark-bg opacity-90"
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
easing: cubicOut,
|
easing: cubicOut,
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
await progress.set(90);
|
progress.set(90);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let removeNotificationTimeout: ReturnType<typeof setTimeout> | undefined;
|
let removeNotificationTimeout: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
removeNotificationTimeout = setTimeout(discard, notificationInfo.timeout);
|
removeNotificationTimeout = setTimeout(discard, notificationInfo.timeout);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePromiseError(update(target));
|
update(target);
|
||||||
return {
|
return {
|
||||||
update,
|
update,
|
||||||
destroy,
|
destroy,
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import { tweened } from 'svelte/motion';
|
import { tweened } from 'svelte/motion';
|
||||||
|
|
||||||
@@ -26,14 +24,14 @@
|
|||||||
|
|
||||||
export let duration = 5;
|
export let duration = 5;
|
||||||
|
|
||||||
const onChange = async () => {
|
const onChange = () => {
|
||||||
progress = setDuration(duration);
|
progress = setDuration(duration);
|
||||||
await play();
|
play();
|
||||||
};
|
};
|
||||||
|
|
||||||
let progress = setDuration(duration);
|
let progress = setDuration(duration);
|
||||||
|
|
||||||
$: duration, handlePromiseError(onChange());
|
$: duration, onChange();
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($progress === 1) {
|
if ($progress === 1) {
|
||||||
@@ -47,35 +45,35 @@
|
|||||||
paused: void;
|
paused: void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
if (autoplay) {
|
if (autoplay) {
|
||||||
await play();
|
play();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const play = async () => {
|
export const play = () => {
|
||||||
status = ProgressBarStatus.Playing;
|
status = ProgressBarStatus.Playing;
|
||||||
dispatch('playing');
|
dispatch('playing');
|
||||||
await progress.set(1);
|
progress.set(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pause = async () => {
|
export const pause = () => {
|
||||||
status = ProgressBarStatus.Paused;
|
status = ProgressBarStatus.Paused;
|
||||||
dispatch('paused');
|
dispatch('paused');
|
||||||
await progress.set($progress);
|
progress.set($progress);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const restart = async (autoplay: boolean) => {
|
export const restart = (autoplay: boolean) => {
|
||||||
await progress.set(0);
|
progress.set(0);
|
||||||
|
|
||||||
if (autoplay) {
|
if (autoplay) {
|
||||||
await play();
|
play();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reset = async () => {
|
export const reset = () => {
|
||||||
status = ProgressBarStatus.Paused;
|
status = ProgressBarStatus.Paused;
|
||||||
await progress.set(0);
|
progress.set(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
function setDuration(newDuration: number) {
|
function setDuration(newDuration: number) {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
import SearchFilterBox from './search-filter-box.svelte';
|
import SearchFilterBox from './search-filter-box.svelte';
|
||||||
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
|
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
|
||||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
|
|
||||||
export let value = '';
|
export let value = '';
|
||||||
export let grayTheme: boolean;
|
export let grayTheme: boolean;
|
||||||
@@ -22,13 +21,13 @@
|
|||||||
let showFilter = false;
|
let showFilter = false;
|
||||||
$: showClearIcon = value.length > 0;
|
$: showClearIcon = value.length > 0;
|
||||||
|
|
||||||
const onSearch = async (payload: SmartSearchDto | MetadataSearchDto) => {
|
const onSearch = (payload: SmartSearchDto | MetadataSearchDto) => {
|
||||||
const params = getMetadataSearchQuery(payload);
|
const params = getMetadataSearchQuery(payload);
|
||||||
|
|
||||||
showHistory = false;
|
showHistory = false;
|
||||||
showFilter = false;
|
showFilter = false;
|
||||||
$isSearchEnabled = false;
|
$isSearchEnabled = false;
|
||||||
await goto(`${AppRoute.SEARCH}?${params}`);
|
goto(`${AppRoute.SEARCH}?${params}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSearchTerm = (searchTerm: string) => {
|
const clearSearchTerm = (searchTerm: string) => {
|
||||||
@@ -64,9 +63,9 @@
|
|||||||
showFilter = false;
|
showFilter = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onHistoryTermClick = async (searchTerm: string) => {
|
const onHistoryTermClick = (searchTerm: string) => {
|
||||||
const searchPayload = { query: searchTerm };
|
const searchPayload = { query: searchTerm };
|
||||||
await onSearch(searchPayload);
|
onSearch(searchPayload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFilterClick = () => {
|
const onFilterClick = () => {
|
||||||
@@ -79,7 +78,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
handlePromiseError(onSearch({ query: value }));
|
onSearch({ query: value });
|
||||||
saveSearchTerm(value);
|
saveSearchTerm(value);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -142,7 +141,7 @@
|
|||||||
<SearchHistoryBox
|
<SearchHistoryBox
|
||||||
on:clearAllSearchTerms={clearAllSearchTerms}
|
on:clearAllSearchTerms={clearAllSearchTerms}
|
||||||
on:clearSearchTerm={({ detail: searchTerm }) => clearSearchTerm(searchTerm)}
|
on:clearSearchTerm={({ detail: searchTerm }) => clearSearchTerm(searchTerm)}
|
||||||
on:selectSearchTerm={({ detail: searchTerm }) => handlePromiseError(onHistoryTermClick(searchTerm))}
|
on:selectSearchTerm={({ detail: searchTerm }) => onHistoryTermClick(searchTerm)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SearchSuggestionType, getSearchSuggestions } from '@immich/sdk';
|
import { SearchSuggestionType, getSearchSuggestions } from '@immich/sdk';
|
||||||
import Combobox, { toComboBoxOptions } from '../combobox.svelte';
|
import Combobox, { toComboBoxOptions } from '../combobox.svelte';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
|
|
||||||
export let filters: SearchCameraFilter;
|
export let filters: SearchCameraFilter;
|
||||||
|
|
||||||
@@ -17,8 +16,8 @@
|
|||||||
|
|
||||||
$: makeFilter = filters.make;
|
$: makeFilter = filters.make;
|
||||||
$: modelFilter = filters.model;
|
$: modelFilter = filters.model;
|
||||||
$: handlePromiseError(updateMakes(modelFilter));
|
$: updateMakes(modelFilter);
|
||||||
$: handlePromiseError(updateModels(makeFilter));
|
$: updateModels(makeFilter);
|
||||||
|
|
||||||
async function updateMakes(model?: string) {
|
async function updateMakes(model?: string) {
|
||||||
makes = await getSearchSuggestions({
|
makes = await getSearchSuggestions({
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const search = () => {
|
const search = async () => {
|
||||||
if (filter.context && filter.personIds.size > 0) {
|
if (filter.context && filter.personIds.size > 0) {
|
||||||
handleError(
|
handleError(
|
||||||
new Error('Context search does not support people filter'),
|
new Error('Context search does not support people filter'),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getSearchSuggestions, SearchSuggestionType } from '@immich/sdk';
|
import { getSearchSuggestions, SearchSuggestionType } from '@immich/sdk';
|
||||||
import Combobox, { toComboBoxOptions } from '../combobox.svelte';
|
import Combobox, { toComboBoxOptions } from '../combobox.svelte';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
|
|
||||||
export let filters: SearchLocationFilter;
|
export let filters: SearchLocationFilter;
|
||||||
|
|
||||||
@@ -19,9 +18,9 @@
|
|||||||
|
|
||||||
$: countryFilter = filters.country;
|
$: countryFilter = filters.country;
|
||||||
$: stateFilter = filters.state;
|
$: stateFilter = filters.state;
|
||||||
$: handlePromiseError(updateCountries());
|
$: updateCountries();
|
||||||
$: handlePromiseError(updateStates(countryFilter));
|
$: updateStates(countryFilter);
|
||||||
$: handlePromiseError(updateCities(countryFilter, stateFilter));
|
$: updateCities(countryFilter, stateFilter);
|
||||||
|
|
||||||
async function updateCountries() {
|
async function updateCountries() {
|
||||||
countries = await getSearchSuggestions({
|
countries = await getSearchSuggestions({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { QueryParameter } from '$lib/constants';
|
import { QueryParameter } from '$lib/constants';
|
||||||
import { hasParamValue, handlePromiseError, updateParamList } from '$lib/utils';
|
import { hasParamValue, updateParamList } from '$lib/utils';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
export let title: string;
|
export let title: string;
|
||||||
@@ -12,12 +12,12 @@
|
|||||||
const syncFromUrl = () => (isOpen = hasParamValue(QueryParameter.IS_OPEN, key));
|
const syncFromUrl = () => (isOpen = hasParamValue(QueryParameter.IS_OPEN, key));
|
||||||
const syncToUrl = (isOpen: boolean) => updateParamList({ param: QueryParameter.IS_OPEN, value: key, add: isOpen });
|
const syncToUrl = (isOpen: boolean) => updateParamList({ param: QueryParameter.IS_OPEN, value: key, add: isOpen });
|
||||||
|
|
||||||
isOpen ? handlePromiseError(syncToUrl(true)) : syncFromUrl();
|
isOpen ? syncToUrl(true) : syncFromUrl();
|
||||||
$: $page.url && syncFromUrl();
|
$: $page.url && syncFromUrl();
|
||||||
|
|
||||||
const toggle = async () => {
|
const toggle = () => {
|
||||||
isOpen = !isOpen;
|
isOpen = !isOpen;
|
||||||
await syncToUrl(isOpen);
|
syncToUrl(isOpen);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
|
|
||||||
export let uploadAsset: UploadAsset;
|
export let uploadAsset: UploadAsset;
|
||||||
|
|
||||||
const handleRetry = async (uploadAsset: UploadAsset) => {
|
const handleRetry = (uploadAsset: UploadAsset) => {
|
||||||
uploadAssetsStore.removeUploadAsset(uploadAsset.id);
|
uploadAssetsStore.removeUploadAsset(uploadAsset.id);
|
||||||
await fileUploadHandler([uploadAsset.file], uploadAsset.albumId);
|
fileUploadHandler([uploadAsset.file], uploadAsset.albumId);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
@@ -86,7 +87,7 @@
|
|||||||
{/await}
|
{/await}
|
||||||
{:else}
|
{:else}
|
||||||
<enhanced:img
|
<enhanced:img
|
||||||
src="$lib/assets/no-thumbnail.png"
|
src={noThumbnailUrl}
|
||||||
alt={'Album without assets'}
|
alt={'Album without assets'}
|
||||||
class="h-[100px] w-[100px] rounded-lg object-cover"
|
class="h-[100px] w-[100px] rounded-lg object-cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
|||||||
@@ -56,8 +56,8 @@
|
|||||||
let selectedLibraryIndex = 0;
|
let selectedLibraryIndex = 0;
|
||||||
let selectedLibrary: LibraryResponseDto | null = null;
|
let selectedLibrary: LibraryResponseDto | null = null;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
await readLibraryList();
|
readLibraryList();
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeAll = () => {
|
const closeAll = () => {
|
||||||
@@ -234,11 +234,11 @@
|
|||||||
updateLibraryIndex = selectedLibraryIndex;
|
updateLibraryIndex = selectedLibraryIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onScanNewLibraryClicked = async () => {
|
const onScanNewLibraryClicked = () => {
|
||||||
closeAll();
|
closeAll();
|
||||||
|
|
||||||
if (selectedLibrary) {
|
if (selectedLibrary) {
|
||||||
await handleScan(selectedLibrary.id);
|
handleScan(selectedLibrary.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -248,38 +248,38 @@
|
|||||||
updateLibraryIndex = selectedLibraryIndex;
|
updateLibraryIndex = selectedLibraryIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onScanAllLibraryFilesClicked = async () => {
|
const onScanAllLibraryFilesClicked = () => {
|
||||||
closeAll();
|
closeAll();
|
||||||
if (selectedLibrary) {
|
if (selectedLibrary) {
|
||||||
await handleScanChanges(selectedLibrary.id);
|
handleScanChanges(selectedLibrary.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onForceScanAllLibraryFilesClicked = async () => {
|
const onForceScanAllLibraryFilesClicked = () => {
|
||||||
closeAll();
|
closeAll();
|
||||||
if (selectedLibrary) {
|
if (selectedLibrary) {
|
||||||
await handleForceScan(selectedLibrary.id);
|
handleForceScan(selectedLibrary.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemoveOfflineFilesClicked = async () => {
|
const onRemoveOfflineFilesClicked = () => {
|
||||||
closeAll();
|
closeAll();
|
||||||
if (selectedLibrary) {
|
if (selectedLibrary) {
|
||||||
await handleRemoveOffline(selectedLibrary.id);
|
handleRemoveOffline(selectedLibrary.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteLibraryClicked = async () => {
|
const onDeleteLibraryClicked = () => {
|
||||||
closeAll();
|
closeAll();
|
||||||
|
|
||||||
if (selectedLibrary && confirm(`Are you sure you want to delete ${selectedLibrary.name} library?`) == true) {
|
if (selectedLibrary && confirm(`Are you sure you want to delete ${selectedLibrary.name} library?`) == true) {
|
||||||
await refreshStats(selectedLibraryIndex);
|
refreshStats(selectedLibraryIndex);
|
||||||
if (totalCount[selectedLibraryIndex] > 0) {
|
if (totalCount[selectedLibraryIndex] > 0) {
|
||||||
deleteAssetCount = totalCount[selectedLibraryIndex];
|
deleteAssetCount = totalCount[selectedLibraryIndex];
|
||||||
confirmDeleteLibrary = selectedLibrary;
|
confirmDeleteLibrary = selectedLibrary;
|
||||||
} else {
|
} else {
|
||||||
deletedLibrary = selectedLibrary;
|
deletedLibrary = selectedLibrary;
|
||||||
await handleDelete();
|
handleDelete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -348,27 +348,27 @@
|
|||||||
|
|
||||||
{#if showContextMenu}
|
{#if showContextMenu}
|
||||||
<Portal target="body">
|
<Portal target="body">
|
||||||
<ContextMenu {...contextMenuPosition} on:outclick={onMenuExit}>
|
<ContextMenu {...contextMenuPosition} on:outclick={() => onMenuExit()}>
|
||||||
<MenuOption on:click={onRenameClicked} text={`Rename`} />
|
<MenuOption on:click={() => onRenameClicked()} text={`Rename`} />
|
||||||
|
|
||||||
{#if selectedLibrary && selectedLibrary.type === LibraryType.External}
|
{#if selectedLibrary && selectedLibrary.type === LibraryType.External}
|
||||||
<MenuOption on:click={onEditImportPathClicked} text="Edit Import Paths" />
|
<MenuOption on:click={() => onEditImportPathClicked()} text="Edit Import Paths" />
|
||||||
<MenuOption on:click={onScanSettingClicked} text="Scan Settings" />
|
<MenuOption on:click={() => onScanSettingClicked()} text="Scan Settings" />
|
||||||
<hr />
|
<hr />
|
||||||
<MenuOption on:click={onScanNewLibraryClicked} text="Scan New Library Files" />
|
<MenuOption on:click={() => onScanNewLibraryClicked()} text="Scan New Library Files" />
|
||||||
<MenuOption
|
<MenuOption
|
||||||
on:click={onScanAllLibraryFilesClicked}
|
on:click={() => onScanAllLibraryFilesClicked()}
|
||||||
text="Re-scan All Library Files"
|
text="Re-scan All Library Files"
|
||||||
subtitle={'Only refreshes modified files'}
|
subtitle={'Only refreshes modified files'}
|
||||||
/>
|
/>
|
||||||
<MenuOption
|
<MenuOption
|
||||||
on:click={onForceScanAllLibraryFilesClicked}
|
on:click={() => onForceScanAllLibraryFilesClicked()}
|
||||||
text="Force Re-scan All Library Files"
|
text="Force Re-scan All Library Files"
|
||||||
subtitle={'Refreshes every file'}
|
subtitle={'Refreshes every file'}
|
||||||
/>
|
/>
|
||||||
<hr />
|
<hr />
|
||||||
<MenuOption on:click={onRemoveOfflineFilesClicked} text="Remove Offline Files" />
|
<MenuOption on:click={() => onRemoveOfflineFilesClicked()} text="Remove Offline Files" />
|
||||||
<MenuOption on:click={onDeleteLibraryClicked}>
|
<MenuOption on:click={() => onDeleteLibraryClicked()}>
|
||||||
<p class="text-red-600">Delete library</p>
|
<p class="text-red-600">Delete library</p>
|
||||||
</MenuOption>
|
</MenuOption>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to link OAuth account');
|
handleError(error, 'Unable to link OAuth account');
|
||||||
} finally {
|
} finally {
|
||||||
await goto('?open=oauth');
|
goto('?open=oauth');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
let removePartnerDto: PartnerResponseDto | null = null;
|
let removePartnerDto: PartnerResponseDto | null = null;
|
||||||
let partners: Array<PartnerSharing> = [];
|
let partners: Array<PartnerSharing> = [];
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
await refreshPartners();
|
refreshPartners();
|
||||||
});
|
});
|
||||||
|
|
||||||
const refreshPartners = async () => {
|
const refreshPartners = async () => {
|
||||||
|
|||||||
@@ -172,17 +172,15 @@ export class AssetStore {
|
|||||||
this.emit(false);
|
this.emit(false);
|
||||||
|
|
||||||
let height = 0;
|
let height = 0;
|
||||||
const loaders = [];
|
|
||||||
for (const bucket of this.buckets) {
|
for (const bucket of this.buckets) {
|
||||||
if (height < viewport.height) {
|
if (height < viewport.height) {
|
||||||
height += bucket.bucketHeight;
|
height += bucket.bucketHeight;
|
||||||
loaders.push(this.loadBucket(bucket.bucketDate, BucketPosition.Visible));
|
this.loadBucket(bucket.bucketDate, BucketPosition.Visible);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
await Promise.all(loaders);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadBucket(bucketDate: string, position: BucketPosition): Promise<void> {
|
async loadBucket(bucketDate: string, position: BucketPosition): Promise<void> {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ websocket
|
|||||||
.on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion))
|
.on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion))
|
||||||
.on('connect_error', (e) => console.log('Websocket Connect Error', e));
|
.on('connect_error', (e) => console.log('Websocket Connect Error', e));
|
||||||
|
|
||||||
export const openWebsocketConnection = () => {
|
export const openWebsocketConnection = async () => {
|
||||||
try {
|
try {
|
||||||
if (!get(user)) {
|
if (!get(user)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -194,13 +194,3 @@ export const findLocale = (code: string | undefined) => {
|
|||||||
name: language?.name,
|
name: language?.name,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const asyncTimeout = (ms: number) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, ms);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handlePromiseError = <T>(promise: Promise<T>): void => {
|
|
||||||
promise.catch((error) => console.error(`[utils.ts]:handlePromiseError ${error}`, error));
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -28,14 +28,10 @@ describe('Executor Queue test', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// The first 3 should be finished within 200ms (concurrency 3)
|
// The first 3 should be finished within 200ms (concurrency 3)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
eq.addTask(() => timeoutPromiseBuilder(100, 'T1'));
|
eq.addTask(() => timeoutPromiseBuilder(100, 'T1'));
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
eq.addTask(() => timeoutPromiseBuilder(200, 'T2'));
|
eq.addTask(() => timeoutPromiseBuilder(200, 'T2'));
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
eq.addTask(() => timeoutPromiseBuilder(150, 'T3'));
|
eq.addTask(() => timeoutPromiseBuilder(150, 'T3'));
|
||||||
// The last task will be executed after 200ms and will finish at 400ms
|
// The last task will be executed after 200ms and will finish at 400ms
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
eq.addTask(() => timeoutPromiseBuilder(200, 'T4'));
|
eq.addTask(() => timeoutPromiseBuilder(200, 'T4'));
|
||||||
|
|
||||||
expect(finished).not.toBeCalled();
|
expect(finished).not.toBeCalled();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user