Compare commits
1 Commits
v1.135.1
...
fix/map-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c7a29b2f4 |
@@ -55,7 +55,7 @@
|
|||||||
"userEnvProbe": "loginInteractiveShell",
|
"userEnvProbe": "loginInteractiveShell",
|
||||||
"remoteEnv": {
|
"remoteEnv": {
|
||||||
// The location where your uploaded files are stored
|
// The location where your uploaded files are stored
|
||||||
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}",
|
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:upload-devcontainer-volume}",
|
||||||
// Connection secret for postgres. You should change it to a random password
|
// Connection secret for postgres. You should change it to a random password
|
||||||
// Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
// Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||||
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",
|
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",
|
||||||
|
|||||||
@@ -51,19 +51,14 @@ fix_permissions() {
|
|||||||
|
|
||||||
run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} +
|
run_cmd sudo find "${IMMICH_WORKSPACE}/server/upload" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres/*" -not -path "${IMMICH_WORKSPACE}/server/upload/postgres" -exec chown node {} +
|
||||||
|
|
||||||
# Change ownership for directories that exist
|
run_cmd sudo chown node -R "${IMMICH_WORKSPACE}/.vscode" \
|
||||||
for dir in "${IMMICH_WORKSPACE}/.vscode" \
|
|
||||||
"${IMMICH_WORKSPACE}/cli/node_modules" \
|
"${IMMICH_WORKSPACE}/cli/node_modules" \
|
||||||
"${IMMICH_WORKSPACE}/e2e/node_modules" \
|
"${IMMICH_WORKSPACE}/e2e/node_modules" \
|
||||||
"${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \
|
"${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \
|
||||||
"${IMMICH_WORKSPACE}/server/node_modules" \
|
"${IMMICH_WORKSPACE}/server/node_modules" \
|
||||||
"${IMMICH_WORKSPACE}/server/dist" \
|
"${IMMICH_WORKSPACE}/server/dist" \
|
||||||
"${IMMICH_WORKSPACE}/web/node_modules" \
|
"${IMMICH_WORKSPACE}/web/node_modules" \
|
||||||
"${IMMICH_WORKSPACE}/web/dist"; do
|
"${IMMICH_WORKSPACE}/web/dist"
|
||||||
if [ -d "$dir" ]; then
|
|
||||||
run_cmd sudo chown node -R "$dir"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
log ""
|
log ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ services:
|
|||||||
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
|
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
|
||||||
- server_node_modules:/workspaces/immich/server/node_modules
|
- server_node_modules:/workspaces/immich/server/node_modules
|
||||||
- web_node_modules:/workspaces/immich/web/node_modules
|
- web_node_modules:/workspaces/immich/web/node_modules
|
||||||
- ${UPLOAD_LOCATION:-upload1-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/workspaces/immich/server/upload
|
- ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload
|
||||||
- ${UPLOAD_LOCATION:-upload2-devcontainer-volume}${UPLOAD_LOCATION:+/photos/upload}:/workspaces/immich/server/upload/upload
|
- ${UPLOAD_LOCATION-./Library}/photos/upload:/workspaces/immich/server/upload/upload
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
|
||||||
immich-web:
|
immich-web:
|
||||||
@@ -29,9 +29,8 @@ services:
|
|||||||
POSTGRES_USER: ${DB_USERNAME-postgres}
|
POSTGRES_USER: ${DB_USERNAME-postgres}
|
||||||
POSTGRES_DB: ${DB_DATABASE_NAME-immich}
|
POSTGRES_DB: ${DB_DATABASE_NAME-immich}
|
||||||
POSTGRES_INITDB_ARGS: '--data-checksums'
|
POSTGRES_INITDB_ARGS: '--data-checksums'
|
||||||
POSTGRES_HOST_AUTH_METHOD: md5
|
volumes:
|
||||||
volumes:
|
- ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data
|
||||||
- ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
env_file: !reset []
|
env_file: !reset []
|
||||||
@@ -43,6 +42,4 @@ volumes:
|
|||||||
open_api_node_modules:
|
open_api_node_modules:
|
||||||
server_node_modules:
|
server_node_modules:
|
||||||
web_node_modules:
|
web_node_modules:
|
||||||
upload1-devcontainer-volume:
|
upload-devcontainer-volume:
|
||||||
upload2-devcontainer-volume:
|
|
||||||
postgres-devcontainer-volume:
|
|
||||||
|
|||||||
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.70",
|
"version": "2.2.69",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.70",
|
"version": "2.2.69",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^4.0.3",
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.70",
|
"version": "2.2.69",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ name: immich-dev
|
|||||||
services:
|
services:
|
||||||
immich-server:
|
immich-server:
|
||||||
container_name: immich_server
|
container_name: immich_server
|
||||||
command: ['/usr/src/app/bin/immich-dev']
|
command: [ '/usr/src/app/bin/immich-dev' ]
|
||||||
image: immich-server-dev:latest
|
image: immich-server-dev:latest
|
||||||
# extends:
|
# extends:
|
||||||
# file: hwaccel.transcoding.yml
|
# file: hwaccel.transcoding.yml
|
||||||
@@ -70,7 +70,7 @@ services:
|
|||||||
# user: 0:0
|
# user: 0:0
|
||||||
build:
|
build:
|
||||||
context: ../web
|
context: ../web
|
||||||
command: ['/usr/src/app/bin/immich-web']
|
command: [ '/usr/src/app/bin/immich-web' ]
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
ports:
|
ports:
|
||||||
@@ -122,7 +122,7 @@ services:
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.2-pgvectors0.2.0
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ services:
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.2-pgvectors0.2.0
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@@ -91,7 +91,7 @@ services:
|
|||||||
# add data source for http://immich-prometheus:9090 to get started
|
# add data source for http://immich-prometheus:9090 to get started
|
||||||
immich-grafana:
|
immich-grafana:
|
||||||
container_name: immich_grafana
|
container_name: immich_grafana
|
||||||
command: ['./run.sh', '-disable-reporting']
|
command: [ './run.sh', '-disable-reporting' ]
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
image: grafana/grafana:12.0.1-ubuntu@sha256:65575bb9c761335e2ff30e364f21d38632e3b2e75f5f81d83cc92f44b9bbc055
|
image: grafana/grafana:12.0.1-ubuntu@sha256:65575bb9c761335e2ff30e364f21d38632e3b2e75f5f81d83cc92f44b9bbc055
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ services:
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.2-pgvectors0.2.0
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.1-pgvectors0.2.0
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
POSTGRES_USER: ${DB_USERNAME}
|
POSTGRES_USER: ${DB_USERNAME}
|
||||||
|
|||||||
@@ -64,13 +64,7 @@ COMMIT;
|
|||||||
|
|
||||||
### Updating VectorChord
|
### Updating VectorChord
|
||||||
|
|
||||||
When installing a new version of VectorChord, you will need to manually update the extension and reindex by connecting to the Immich database and running:
|
When installing a new version of VectorChord, you will need to manually update the extension by connecting to the Immich database and running `ALTER EXTENSION vchord UPDATE;`.
|
||||||
|
|
||||||
```
|
|
||||||
ALTER EXTENSION vchord UPDATE;
|
|
||||||
REINDEX INDEX face_index;
|
|
||||||
REINDEX INDEX clip_index;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migrating to VectorChord
|
## Migrating to VectorChord
|
||||||
|
|
||||||
@@ -82,8 +76,6 @@ Support for pgvecto.rs will be dropped in a later release, hence we recommend al
|
|||||||
|
|
||||||
The easiest option is to have both extensions installed during the migration:
|
The easiest option is to have both extensions installed during the migration:
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Migration steps (automatic)</summary>
|
|
||||||
1. Ensure you still have pgvecto.rs installed
|
1. Ensure you still have pgvecto.rs installed
|
||||||
2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`)
|
2. Install `pgvector` (`>= 0.7.0, < 1.0.0`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`)
|
||||||
3. [Install VectorChord][vchord-install]
|
3. [Install VectorChord][vchord-install]
|
||||||
@@ -97,12 +89,8 @@ The easiest option is to have both extensions installed during the migration:
|
|||||||
11. Restart the Postgres database
|
11. Restart the Postgres database
|
||||||
12. Uninstall pgvecto.rs (e.g. `apt-get purge vectors-pg14` on Debian-based environments, replacing `pg14` as appropriate). `pgvector` must remain installed as it provides the data types used by `vchord`
|
12. Uninstall pgvecto.rs (e.g. `apt-get purge vectors-pg14` on Debian-based environments, replacing `pg14` as appropriate). `pgvector` must remain installed as it provides the data types used by `vchord`
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
If it is not possible to have both VectorChord and pgvecto.rs installed at the same time, you can perform the migration with more manual steps:
|
If it is not possible to have both VectorChord and pgvecto.rs installed at the same time, you can perform the migration with more manual steps:
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Migration steps (manual)</summary>
|
|
||||||
1. While pgvecto.rs is still installed, run the following SQL command using psql or your choice of database client. Take note of the number outputted by this command as you will need it later
|
1. While pgvecto.rs is still installed, run the following SQL command using psql or your choice of database client. Take note of the number outputted by this command as you will need it later
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -135,20 +123,14 @@ ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512);
|
|||||||
|
|
||||||
5. Start Immich and let it create new indices using VectorChord
|
5. Start Immich and let it create new indices using VectorChord
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### Migrating from pgvector
|
### Migrating from pgvector
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Migration steps</summary>
|
|
||||||
1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client
|
1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client
|
||||||
2. Follow the Prerequisites to install VectorChord
|
2. Follow the Prerequisites to install VectorChord
|
||||||
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;`
|
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;`
|
||||||
4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set
|
4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set
|
||||||
5. Start Immich and let it create new indices using VectorChord
|
5. Start Immich and let it create new indices using VectorChord
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
Note that VectorChord itself uses pgvector types, so you should not uninstall pgvector after following these steps.
|
Note that VectorChord itself uses pgvector types, so you should not uninstall pgvector after following these steps.
|
||||||
|
|
||||||
[vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html
|
[vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html
|
||||||
|
|||||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,8 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"label": "v1.135.1",
|
|
||||||
"url": "https://v1.135.1.archive.immich.app"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "v1.135.0",
|
"label": "v1.135.0",
|
||||||
"url": "https://v1.135.0.archive.immich.app"
|
"url": "https://v1.135.0.archive.immich.app"
|
||||||
|
|||||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
},
|
},
|
||||||
"../cli": {
|
"../cli": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.70",
|
"version": "2.2.69",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -14,11 +14,7 @@ describe('/people', () => {
|
|||||||
let nameAlicePerson: PersonResponseDto;
|
let nameAlicePerson: PersonResponseDto;
|
||||||
let nameBobPerson: PersonResponseDto;
|
let nameBobPerson: PersonResponseDto;
|
||||||
let nameCharliePerson: PersonResponseDto;
|
let nameCharliePerson: PersonResponseDto;
|
||||||
let nameNullPerson4Assets: PersonResponseDto;
|
let nameNullPerson: PersonResponseDto;
|
||||||
let nameNullPerson3Assets: PersonResponseDto;
|
|
||||||
let nameNullPerson1Asset: PersonResponseDto;
|
|
||||||
let nameBillPersonFavourite: PersonResponseDto;
|
|
||||||
let nameFreddyPersonFavourite: PersonResponseDto;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
@@ -31,11 +27,7 @@ describe('/people', () => {
|
|||||||
nameCharliePerson,
|
nameCharliePerson,
|
||||||
nameBobPerson,
|
nameBobPerson,
|
||||||
nameAlicePerson,
|
nameAlicePerson,
|
||||||
nameNullPerson4Assets,
|
nameNullPerson,
|
||||||
nameNullPerson3Assets,
|
|
||||||
nameNullPerson1Asset,
|
|
||||||
nameBillPersonFavourite,
|
|
||||||
nameFreddyPersonFavourite,
|
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
utils.createPerson(admin.accessToken, {
|
utils.createPerson(admin.accessToken, {
|
||||||
name: 'visible_person',
|
name: 'visible_person',
|
||||||
@@ -60,26 +52,11 @@ describe('/people', () => {
|
|||||||
utils.createPerson(admin.accessToken, {
|
utils.createPerson(admin.accessToken, {
|
||||||
name: '',
|
name: '',
|
||||||
}),
|
}),
|
||||||
utils.createPerson(admin.accessToken, {
|
|
||||||
name: '',
|
|
||||||
}),
|
|
||||||
utils.createPerson(admin.accessToken, {
|
|
||||||
name: '',
|
|
||||||
}),
|
|
||||||
utils.createPerson(admin.accessToken, {
|
|
||||||
name: 'Bill',
|
|
||||||
isFavorite: true,
|
|
||||||
}),
|
|
||||||
utils.createPerson(admin.accessToken, {
|
|
||||||
name: 'Freddy',
|
|
||||||
isFavorite: true,
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const asset1 = await utils.createAsset(admin.accessToken);
|
const asset1 = await utils.createAsset(admin.accessToken);
|
||||||
const asset2 = await utils.createAsset(admin.accessToken);
|
const asset2 = await utils.createAsset(admin.accessToken);
|
||||||
const asset3 = await utils.createAsset(admin.accessToken);
|
const asset3 = await utils.createAsset(admin.accessToken);
|
||||||
const asset4 = await utils.createAsset(admin.accessToken);
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }),
|
utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }),
|
||||||
@@ -87,27 +64,15 @@ describe('/people', () => {
|
|||||||
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
|
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
|
||||||
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
|
utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }),
|
||||||
utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }),
|
utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }),
|
||||||
utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }), // 4 assets
|
utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }),
|
||||||
// Named persons
|
// Named persons
|
||||||
utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset
|
utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset
|
||||||
utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }),
|
utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }),
|
||||||
utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets
|
utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets
|
||||||
utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset
|
utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset
|
||||||
// Null-named person 4 assets
|
// Null-named person
|
||||||
utils.createFace({ assetId: asset1.id, personId: nameNullPerson4Assets.id }),
|
utils.createFace({ assetId: asset1.id, personId: nameNullPerson.id }),
|
||||||
utils.createFace({ assetId: asset2.id, personId: nameNullPerson4Assets.id }),
|
utils.createFace({ assetId: asset2.id, personId: nameNullPerson.id }), // 2 assets
|
||||||
utils.createFace({ assetId: asset3.id, personId: nameNullPerson4Assets.id }),
|
|
||||||
utils.createFace({ assetId: asset4.id, personId: nameNullPerson4Assets.id }), // 4 assets
|
|
||||||
// Null-named person 3 assets
|
|
||||||
utils.createFace({ assetId: asset1.id, personId: nameNullPerson3Assets.id }),
|
|
||||||
utils.createFace({ assetId: asset2.id, personId: nameNullPerson3Assets.id }),
|
|
||||||
utils.createFace({ assetId: asset3.id, personId: nameNullPerson3Assets.id }), // 3 assets
|
|
||||||
// Null-named person 1 asset
|
|
||||||
utils.createFace({ assetId: asset3.id, personId: nameNullPerson1Asset.id }),
|
|
||||||
// Favourite People
|
|
||||||
utils.createFace({ assetId: asset1.id, personId: nameFreddyPersonFavourite.id }),
|
|
||||||
utils.createFace({ assetId: asset2.id, personId: nameFreddyPersonFavourite.id }),
|
|
||||||
utils.createFace({ assetId: asset1.id, personId: nameBillPersonFavourite.id }),
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,19 +87,15 @@ describe('/people', () => {
|
|||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
total: 11,
|
total: 7,
|
||||||
hidden: 1,
|
hidden: 1,
|
||||||
people: [
|
people: [
|
||||||
expect.objectContaining({ name: 'Bill' }),
|
|
||||||
expect.objectContaining({ name: 'Freddy' }),
|
|
||||||
expect.objectContaining({ name: 'Alice' }),
|
|
||||||
expect.objectContaining({ name: 'Bob' }),
|
|
||||||
expect.objectContaining({ name: 'Charlie' }),
|
|
||||||
expect.objectContaining({ name: 'multiple_assets_person' }),
|
expect.objectContaining({ name: 'multiple_assets_person' }),
|
||||||
|
expect.objectContaining({ name: 'Bob' }),
|
||||||
|
expect.objectContaining({ name: 'Alice' }),
|
||||||
|
expect.objectContaining({ name: 'Charlie' }),
|
||||||
expect.objectContaining({ name: 'visible_person' }),
|
expect.objectContaining({ name: 'visible_person' }),
|
||||||
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
|
expect.objectContaining({ name: 'hidden_person' }),
|
||||||
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
|
|
||||||
expect.objectContaining({ name: 'hidden_person' }), // Should really be before the null names
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -144,21 +105,17 @@ describe('/people', () => {
|
|||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body.hasNextPage).toBe(false);
|
expect(body.hasNextPage).toBe(false);
|
||||||
expect(body.total).toBe(11); // All persons
|
expect(body.total).toBe(7); // All persons
|
||||||
expect(body.hidden).toBe(1); // 'hidden_person'
|
expect(body.hidden).toBe(1); // 'hidden_person'
|
||||||
|
|
||||||
const people = body.people as PersonResponseDto[];
|
const people = body.people as PersonResponseDto[];
|
||||||
|
|
||||||
expect(people.map((p) => p.id)).toEqual([
|
expect(people.map((p) => p.id)).toEqual([
|
||||||
nameBillPersonFavourite.id, // name: 'Bill', count: 2
|
|
||||||
nameFreddyPersonFavourite.id, // name: 'Freddy', count: 2
|
|
||||||
nameAlicePerson.id, // name: 'Alice', count: 1
|
|
||||||
nameBobPerson.id, // name: 'Bob', count: 2
|
|
||||||
nameCharliePerson.id, // name: 'Charlie', count: 1
|
|
||||||
multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3
|
multipleAssetsPerson.id, // name: 'multiple_assets_person', count: 3
|
||||||
|
nameBobPerson.id, // name: 'Bob', count: 2
|
||||||
|
nameAlicePerson.id, // name: 'Alice', count: 1
|
||||||
|
nameCharliePerson.id, // name: 'Charlie', count: 1
|
||||||
visiblePerson.id, // name: 'visible_person', count: 1
|
visiblePerson.id, // name: 'visible_person', count: 1
|
||||||
nameNullPerson4Assets.id, // name: '', count: 4
|
|
||||||
nameNullPerson3Assets.id, // name: '', count: 3
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(people.some((p) => p.id === hiddenPerson.id)).toBe(false);
|
expect(people.some((p) => p.id === hiddenPerson.id)).toBe(false);
|
||||||
@@ -170,18 +127,14 @@ describe('/people', () => {
|
|||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
total: 11,
|
total: 7,
|
||||||
hidden: 1,
|
hidden: 1,
|
||||||
people: [
|
people: [
|
||||||
expect.objectContaining({ name: 'Bill' }),
|
|
||||||
expect.objectContaining({ name: 'Freddy' }),
|
|
||||||
expect.objectContaining({ name: 'Alice' }),
|
|
||||||
expect.objectContaining({ name: 'Bob' }),
|
|
||||||
expect.objectContaining({ name: 'Charlie' }),
|
|
||||||
expect.objectContaining({ name: 'multiple_assets_person' }),
|
expect.objectContaining({ name: 'multiple_assets_person' }),
|
||||||
|
expect.objectContaining({ name: 'Bob' }),
|
||||||
|
expect.objectContaining({ name: 'Alice' }),
|
||||||
|
expect.objectContaining({ name: 'Charlie' }),
|
||||||
expect.objectContaining({ name: 'visible_person' }),
|
expect.objectContaining({ name: 'visible_person' }),
|
||||||
expect.objectContaining({ id: nameNullPerson4Assets.id, name: '' }),
|
|
||||||
expect.objectContaining({ id: nameNullPerson3Assets.id, name: '' }),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -195,9 +148,9 @@ describe('/people', () => {
|
|||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
hasNextPage: true,
|
hasNextPage: true,
|
||||||
total: 11,
|
total: 7,
|
||||||
hidden: 1,
|
hidden: 1,
|
||||||
people: [expect.objectContaining({ name: 'Charlie' })],
|
people: [expect.objectContaining({ name: 'visible_person' })],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 202,
|
"android.injected.version.code" => 201,
|
||||||
"android.injected.version.name" => "1.135.1",
|
"android.injected.version.name" => "1.135.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 77;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -117,6 +117,8 @@
|
|||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = Sync;
|
path = Sync;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -344,7 +346,6 @@
|
|||||||
};
|
};
|
||||||
F0B57D372DF764BD00DC5BCC = {
|
F0B57D372DF764BD00DC5BCC = {
|
||||||
CreatedOnToolsVersion = 16.4;
|
CreatedOnToolsVersion = 16.4;
|
||||||
ProvisioningStyle = Automatic;
|
|
||||||
};
|
};
|
||||||
FAC6F88F2D287C890078CB2F = {
|
FAC6F88F2D287C890078CB2F = {
|
||||||
CreatedOnToolsVersion = 16.0;
|
CreatedOnToolsVersion = 16.0;
|
||||||
@@ -471,14 +472,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@@ -507,14 +504,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
@@ -655,7 +648,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 208;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -799,7 +792,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 208;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -829,7 +822,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 208;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -863,7 +856,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -871,7 +864,7 @@
|
|||||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -906,7 +899,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -914,7 +907,7 @@
|
|||||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -946,7 +939,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -954,7 +947,7 @@
|
|||||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
INFOPLIST_KEY_CFBundleDisplayName = Widget;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -985,7 +978,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 208;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -1029,7 +1022,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 208;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -1070,7 +1063,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 209;
|
CURRENT_PROJECT_VERSION = 208;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.135.0</string>
|
<string>1.134.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>209</string>
|
<string>208</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true />
|
<true />
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
@@ -115,8 +115,8 @@
|
|||||||
</dict>
|
</dict>
|
||||||
<key>NSBonjourServices</key>
|
<key>NSBonjourServices</key>
|
||||||
<array>
|
<array>
|
||||||
<string>_googlecast._tcp</string>
|
<string>_googlecast._tcp</string>
|
||||||
<string>_CC1AD845._googlecast._tcp</string>
|
<string>_CC1AD845._googlecast._tcp</string>
|
||||||
</array>
|
</array>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>We need to access the camera to let you take beautiful video using this app</string>
|
<string>We need to access the camera to let you take beautiful video using this app</string>
|
||||||
@@ -168,8 +168,5 @@
|
|||||||
<true />
|
<true />
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
<string>We need to use FaceID to allow access to your locked folder</string>
|
<string>We need to use FaceID to allow access to your locked folder</string>
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
|
||||||
<string>We need local network permission to connect to the local server using IP address and
|
|
||||||
allow the casting feature to work</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ struct RandomConfigurationAppIntent: WidgetConfigurationIntent {
|
|||||||
"Choose an album to show images from"
|
"Choose an album to show images from"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parameter(title: "Album")
|
@Parameter(title: "Album", default: NO_ALBUM)
|
||||||
var album: Album?
|
var album: Album?
|
||||||
|
|
||||||
@Parameter(title: "Show Album Name", default: false)
|
@Parameter(title: "Show Album Name", default: false)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ platform :ios do
|
|||||||
path: "./Runner.xcodeproj",
|
path: "./Runner.xcodeproj",
|
||||||
)
|
)
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.135.1"
|
version_number: "1.135.0"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -57,13 +57,7 @@ Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final shouldTruncate = version < 8 || version < targetVersion;
|
final shouldTruncate = version < 8 || version < targetVersion;
|
||||||
|
|
||||||
if (shouldTruncate) {
|
if (shouldTruncate) {
|
||||||
if (targetVersion == 12) {
|
|
||||||
await Store.put(StoreKey.version, targetVersion);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _migrateTo(db, targetVersion);
|
await _migrateTo(db, targetVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@@ -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.135.1
|
- API version: 1.135.0
|
||||||
- Generator version: 7.8.0
|
- Generator version: 7.8.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
|
|||||||
@@ -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.135.1+202
|
version: 1.135.0+201
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
|||||||
@@ -8503,7 +8503,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
|||||||
4
open-api/typescript-sdk/package-lock.json
generated
4
open-api/typescript-sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.135.1
|
* 1.135.0
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
|
|||||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -12,37 +12,6 @@ delete from "person"
|
|||||||
where
|
where
|
||||||
"person"."id" in ($1)
|
"person"."id" in ($1)
|
||||||
|
|
||||||
-- PersonRepository.getAllForUser
|
|
||||||
select
|
|
||||||
"person".*
|
|
||||||
from
|
|
||||||
"person"
|
|
||||||
inner join "asset_faces" on "asset_faces"."personId" = "person"."id"
|
|
||||||
inner join "assets" on "asset_faces"."assetId" = "assets"."id"
|
|
||||||
and "assets"."visibility" = 'timeline'
|
|
||||||
and "assets"."deletedAt" is null
|
|
||||||
where
|
|
||||||
"person"."ownerId" = $1
|
|
||||||
and "asset_faces"."deletedAt" is null
|
|
||||||
and "person"."isHidden" = $2
|
|
||||||
group by
|
|
||||||
"person"."id"
|
|
||||||
having
|
|
||||||
(
|
|
||||||
"person"."name" != $3
|
|
||||||
or count("asset_faces"."assetId") >= $4
|
|
||||||
)
|
|
||||||
order by
|
|
||||||
"person"."isHidden" asc,
|
|
||||||
"person"."isFavorite" desc,
|
|
||||||
NULLIF(person.name, '') asc nulls last,
|
|
||||||
count("asset_faces"."assetId") desc,
|
|
||||||
"person"."createdAt"
|
|
||||||
limit
|
|
||||||
$5
|
|
||||||
offset
|
|
||||||
$6
|
|
||||||
|
|
||||||
-- PersonRepository.getAllWithoutFaces
|
-- PersonRepository.getAllWithoutFaces
|
||||||
select
|
select
|
||||||
"person".*
|
"person".*
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ export class DatabaseRepository {
|
|||||||
await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db);
|
await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db);
|
||||||
if (extension === DatabaseExtension.VECTORCHORD) {
|
if (extension === DatabaseExtension.VECTORCHORD) {
|
||||||
const dbName = sql.id(await this.getDatabaseName());
|
const dbName = sql.id(await this.getDatabaseName());
|
||||||
|
await sql`ALTER DATABASE ${dbName} SET vchordrq.prewarm_dim = '512,640,768,1024,1152,1536'`.execute(this.db);
|
||||||
|
await sql`SET vchordrq.prewarm_dim = '512,640,768,1024,1152,1536'`.execute(this.db);
|
||||||
await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db);
|
await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db);
|
||||||
await sql`SET vchordrq.probes = 1`.execute(this.db);
|
await sql`SET vchordrq.probes = 1`.execute(this.db);
|
||||||
}
|
}
|
||||||
@@ -140,29 +142,21 @@ export class DatabaseRepository {
|
|||||||
}
|
}
|
||||||
targetVersion ??= availableVersion;
|
targetVersion ??= availableVersion;
|
||||||
|
|
||||||
|
const isVectors = extension === DatabaseExtension.VECTORS;
|
||||||
let restartRequired = false;
|
let restartRequired = false;
|
||||||
const diff = semver.diff(installedVersion, targetVersion);
|
const diff = semver.diff(installedVersion, targetVersion);
|
||||||
if (!diff) {
|
|
||||||
return { restartRequired: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
this.db.schema.dropIndex(VectorIndex.CLIP).ifExists().execute(),
|
|
||||||
this.db.schema.dropIndex(VectorIndex.FACE).ifExists().execute(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await this.db.transaction().execute(async (tx) => {
|
await this.db.transaction().execute(async (tx) => {
|
||||||
await this.setSearchPath(tx);
|
await this.setSearchPath(tx);
|
||||||
|
|
||||||
await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx);
|
await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx);
|
||||||
|
|
||||||
if (extension === DatabaseExtension.VECTORS && (diff === 'major' || diff === 'minor')) {
|
if (isVectors && (diff === 'major' || diff === 'minor')) {
|
||||||
await sql`SELECT pgvectors_upgrade()`.execute(tx);
|
await sql`SELECT pgvectors_upgrade()`.execute(tx);
|
||||||
restartRequired = true;
|
restartRequired = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!restartRequired) {
|
if (diff && !restartRequired) {
|
||||||
await Promise.all([this.reindexVectors(VectorIndex.CLIP), this.reindexVectors(VectorIndex.FACE)]);
|
await Promise.all([this.reindexVectors(VectorIndex.CLIP), this.reindexVectors(VectorIndex.FACE)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ export class PersonRepository {
|
|||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] })
|
|
||||||
async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) {
|
async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) {
|
||||||
const items = await this.db
|
const items = await this.db
|
||||||
.selectFrom('person')
|
.selectFrom('person')
|
||||||
@@ -180,8 +179,8 @@ export class PersonRepository {
|
|||||||
)
|
)
|
||||||
.$if(!options?.closestFaceAssetId, (qb) =>
|
.$if(!options?.closestFaceAssetId, (qb) =>
|
||||||
qb
|
qb
|
||||||
.orderBy(sql`NULLIF(person.name, '')`, (om) => om.asc().nullsLast())
|
|
||||||
.orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc')
|
.orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc')
|
||||||
|
.orderBy(sql`NULLIF(person.name, '') asc nulls last`)
|
||||||
.orderBy('person.createdAt'),
|
.orderBy('person.createdAt'),
|
||||||
)
|
)
|
||||||
.$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false))
|
.$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false))
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Kysely, sql } from 'kysely';
|
|
||||||
|
|
||||||
export async function up(db: Kysely<any>): Promise<void> {
|
|
||||||
const { rows } = await sql<{ db: string }>`SELECT current_database() as db;`.execute(db);
|
|
||||||
const databaseName = rows[0].db;
|
|
||||||
await sql.raw(`ALTER DATABASE "${databaseName}" RESET vchordrq.prewarm_dim;`).execute(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(db: Kysely<any>): Promise<void> {
|
|
||||||
const { rows } = await sql<{ db: string }>`SELECT current_database() as db;`.execute(db);
|
|
||||||
const databaseName = rows[0].db;
|
|
||||||
await sql
|
|
||||||
.raw(`ALTER DATABASE "${databaseName}" SET vchordrq.prewarm_dim = '512,640,768,1024,1152,1536';`)
|
|
||||||
.execute(db);
|
|
||||||
}
|
|
||||||
@@ -151,11 +151,11 @@ describe(SearchService.name, () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const id = assetStub.livePhotoMotionAsset.id;
|
const id = assetStub.livePhotoMotionAsset.id;
|
||||||
|
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id });
|
const result = await sut.handleSearchDuplicates({ id });
|
||||||
|
|
||||||
expect(result).toBe(JobStatus.SKIPPED);
|
expect(result).toBe(JobStatus.SKIPPED);
|
||||||
expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip if duplicate detection is disabled', async () => {
|
it('should skip if duplicate detection is disabled', async () => {
|
||||||
@@ -168,11 +168,11 @@ describe(SearchService.name, () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const id = assetStub.livePhotoMotionAsset.id;
|
const id = assetStub.livePhotoMotionAsset.id;
|
||||||
|
mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||||
|
|
||||||
const result = await sut.handleSearchDuplicates({ id });
|
const result = await sut.handleSearchDuplicates({ id });
|
||||||
|
|
||||||
expect(result).toBe(JobStatus.SKIPPED);
|
expect(result).toBe(JobStatus.SKIPPED);
|
||||||
expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail if asset is not found', async () => {
|
it('should fail if asset is not found', async () => {
|
||||||
|
|||||||
@@ -1113,6 +1113,8 @@ describe(LibraryService.name, () => {
|
|||||||
mocks.library.get.mockResolvedValue(library);
|
mocks.library.get.mockResolvedValue(library);
|
||||||
mocks.library.streamAssetIds.mockReturnValue(makeStream([assetStub.image1]));
|
mocks.library.streamAssetIds.mockReturnValue(makeStream([assetStub.image1]));
|
||||||
|
|
||||||
|
mocks.asset.getById.mockResolvedValue(assetStub.image1);
|
||||||
|
|
||||||
await expect(sut.handleDeleteLibrary({ id: library.id })).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleDeleteLibrary({ id: library.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ describe(NotificationService.name, () => {
|
|||||||
mocks.album.getById.mockResolvedValue(albumStub.empty);
|
mocks.album.getById.mockResolvedValue(albumStub.empty);
|
||||||
|
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.SKIPPED);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.SKIPPED);
|
||||||
expect(mocks.job.queue).not.toHaveBeenCalled();
|
expect(mocks.asset.getById).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip if the recipient has email notifications disabled', async () => {
|
it('should skip if the recipient has email notifications disabled', async () => {
|
||||||
|
|||||||
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.135.1",
|
"version": "1.135.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import MapModal from '$lib/modals/MapModal.svelte';
|
import MapModal from '$lib/modals/MapModal.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
import { navigate } from '$lib/utils/navigation';
|
||||||
import { getAlbumInfo, type AlbumResponseDto, type MapMarkerResponseDto } from '@immich/sdk';
|
import { getAlbumInfo, type AlbumResponseDto, type MapMarkerResponseDto } from '@immich/sdk';
|
||||||
import { IconButton } from '@immich/ui';
|
import { IconButton } from '@immich/ui';
|
||||||
import { mdiMapOutline } from '@mdi/js';
|
import { mdiMapOutline } from '@mdi/js';
|
||||||
@@ -14,7 +17,9 @@
|
|||||||
|
|
||||||
let { album }: Props = $props();
|
let { album }: Props = $props();
|
||||||
let abortController: AbortController;
|
let abortController: AbortController;
|
||||||
let { setAssetId } = assetViewingStore;
|
let { isViewing: showAssetViewer, asset: viewingAsset, setAssetId } = assetViewingStore;
|
||||||
|
let viewingAssets: string[] = $state([]);
|
||||||
|
let viewingAssetCursor = 0;
|
||||||
|
|
||||||
let mapMarkers: MapMarkerResponseDto[] = $state([]);
|
let mapMarkers: MapMarkerResponseDto[] = $state([]);
|
||||||
|
|
||||||
@@ -56,9 +61,37 @@
|
|||||||
const assetIds = await modalManager.show(MapModal, { mapMarkers });
|
const assetIds = await modalManager.show(MapModal, { mapMarkers });
|
||||||
|
|
||||||
if (assetIds) {
|
if (assetIds) {
|
||||||
|
viewingAssets = assetIds;
|
||||||
|
viewingAssetCursor = 0;
|
||||||
|
|
||||||
await setAssetId(assetIds[0]);
|
await setAssetId(assetIds[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function navigateNext() {
|
||||||
|
if (viewingAssetCursor < viewingAssets.length - 1) {
|
||||||
|
await setAssetId(viewingAssets[++viewingAssetCursor]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function navigatePrevious() {
|
||||||
|
if (viewingAssetCursor > 0) {
|
||||||
|
await setAssetId(viewingAssets[--viewingAssetCursor]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function navigateRandom() {
|
||||||
|
if (viewingAssets.length <= 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const index = Math.floor(Math.random() * viewingAssets.length);
|
||||||
|
const asset = await setAssetId(viewingAssets[index]);
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -69,3 +102,22 @@
|
|||||||
onclick={openMap}
|
onclick={openMap}
|
||||||
aria-label={$t('map')}
|
aria-label={$t('map')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Portal target="body">
|
||||||
|
{#if $showAssetViewer}
|
||||||
|
{#await import('../../../lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
||||||
|
<AssetViewer
|
||||||
|
asset={$viewingAsset}
|
||||||
|
showNavigation={viewingAssets.length > 1}
|
||||||
|
onNext={navigateNext}
|
||||||
|
onPrevious={navigatePrevious}
|
||||||
|
onRandom={navigateRandom}
|
||||||
|
onClose={() => {
|
||||||
|
assetViewingStore.showAssetViewer(false);
|
||||||
|
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
||||||
|
}}
|
||||||
|
isShared={false}
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
|
{/if}
|
||||||
|
</Portal>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
{@const isCollapsed = isAlbumGroupCollapsed($albumViewSettings, albumGroup.id)}
|
{@const isCollapsed = isAlbumGroupCollapsed($albumViewSettings, albumGroup.id)}
|
||||||
{@const iconRotation = isCollapsed ? 'rotate-0' : 'rotate-90'}
|
{@const iconRotation = isCollapsed ? 'rotate-0' : 'rotate-90'}
|
||||||
<tbody
|
<tbody
|
||||||
class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray dark:text-immich-dark-fg mt-4"
|
class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray dark:text-immich-dark-fg"
|
||||||
>
|
>
|
||||||
<tr
|
<tr
|
||||||
class="flex w-full place-items-center p-2 md:ps-5 md:pe-5 md:pt-3 md:pb-3"
|
class="flex w-full place-items-center p-2 md:ps-5 md:pe-5 md:pt-3 md:pb-3"
|
||||||
|
|||||||
@@ -546,8 +546,11 @@
|
|||||||
|
|
||||||
{#if stack && withStacked}
|
{#if stack && withStacked}
|
||||||
{@const stackedAssets = stack.assets}
|
{@const stackedAssets = stack.assets}
|
||||||
<div id="stack-slideshow" class="absolute bottom-0 w-full col-span-4 col-start-1">
|
<div
|
||||||
<div class="relative flex flex-row no-wrap overflow-x-auto overflow-y-hidden horizontal-scrollbar">
|
id="stack-slideshow"
|
||||||
|
class="flex place-item-center place-content-center absolute bottom-0 w-full col-span-4 col-start-1 overflow-x-auto overflow-y-hidden horizontal-scrollbar"
|
||||||
|
>
|
||||||
|
<div class="relative flex flex-row no-wrap">
|
||||||
{#each stackedAssets as stackedAsset (stackedAsset.id)}
|
{#each stackedAssets as stackedAsset (stackedAsset.id)}
|
||||||
<div
|
<div
|
||||||
class={['inline-block px-1 relative transition-all pb-2']}
|
class={['inline-block px-1 relative transition-all pb-2']}
|
||||||
|
|||||||
@@ -108,30 +108,6 @@
|
|||||||
}
|
}
|
||||||
await modalManager.show(SlideshowSettingsModal);
|
await modalManager.show(SlideshowSettingsModal);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
function exitFullscreenHandler() {
|
|
||||||
const doc = document as Document & {
|
|
||||||
webkitIsFullScreen?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
// eslint-disable-next-line tscompat/tscompat
|
|
||||||
!document.fullscreenElement &&
|
|
||||||
!doc.webkitIsFullScreen
|
|
||||||
) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('fullscreenchange', exitFullscreenHandler);
|
|
||||||
document.addEventListener('webkitfullscreenchange', exitFullscreenHandler);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('fullscreenchange', exitFullscreenHandler);
|
|
||||||
document.removeEventListener('webkitfullscreenchange', exitFullscreenHandler);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document
|
<svelte:document
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
||||||
import Scrubber from '$lib/components/shared-components/scrubber/scrubber.svelte';
|
import Scrubber from '$lib/components/shared-components/scrubber/scrubber.svelte';
|
||||||
import { AppRoute, AssetAction } from '$lib/constants';
|
import { AppRoute, AssetAction } from '$lib/constants';
|
||||||
|
import { albumMapViewManager } from '$lib/managers/album-view-map.manager.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
||||||
@@ -910,26 +911,28 @@
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Portal target="body">
|
{#if !albumMapViewManager.isInMapView}
|
||||||
{#if $showAssetViewer}
|
<Portal target="body">
|
||||||
{#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
{#if $showAssetViewer}
|
||||||
<AssetViewer
|
{#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
||||||
{withStacked}
|
<AssetViewer
|
||||||
asset={$viewingAsset}
|
{withStacked}
|
||||||
preloadAssets={$preloadAssets}
|
asset={$viewingAsset}
|
||||||
{isShared}
|
preloadAssets={$preloadAssets}
|
||||||
{album}
|
{isShared}
|
||||||
{person}
|
{album}
|
||||||
preAction={handlePreAction}
|
{person}
|
||||||
onAction={handleAction}
|
preAction={handlePreAction}
|
||||||
onPrevious={handlePrevious}
|
onAction={handleAction}
|
||||||
onNext={handleNext}
|
onPrevious={handlePrevious}
|
||||||
onRandom={handleRandom}
|
onNext={handleNext}
|
||||||
onClose={handleClose}
|
onRandom={handleRandom}
|
||||||
/>
|
onClose={handleClose}
|
||||||
{/await}
|
/>
|
||||||
{/if}
|
{/await}
|
||||||
</Portal>
|
{/if}
|
||||||
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#asset-grid {
|
#asset-grid {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
mapMarkers = $bindable(),
|
mapMarkers = $bindable(),
|
||||||
showSettings = true,
|
showSettings = true,
|
||||||
zoom = undefined,
|
zoom = undefined,
|
||||||
center = $bindable(undefined),
|
center = undefined,
|
||||||
hash = false,
|
hash = false,
|
||||||
simplified = false,
|
simplified = false,
|
||||||
clickable = false,
|
clickable = false,
|
||||||
@@ -250,12 +250,14 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let lastCenter: LngLatLike | undefined = undefined;
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!center || !zoom) {
|
if (!map || !center || isEqual(lastCenter, center)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
map?.jumpTo({ center, zoom });
|
||||||
untrack(() => map?.jumpTo({ center, zoom }));
|
lastCenter = center;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
13
web/src/lib/managers/album-view-map.manager.svelte.ts
Normal file
13
web/src/lib/managers/album-view-map.manager.svelte.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class AlbumMapViewManager {
|
||||||
|
#isInMapView = $state(false);
|
||||||
|
|
||||||
|
get isInMapView() {
|
||||||
|
return this.#isInMapView;
|
||||||
|
}
|
||||||
|
|
||||||
|
set isInMapView(isInMapView: boolean) {
|
||||||
|
this.#isInMapView = isInMapView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const albumMapViewManager = new AlbumMapViewManager();
|
||||||
@@ -343,11 +343,7 @@ export class MonthGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findAssetById(assetDescriptor: AssetDescriptor) {
|
findAssetById(assetDescriptor: AssetDescriptor) {
|
||||||
for (const asset of this.assetsIterator()) {
|
return this.assetsIterator().find((asset) => asset.id === assetDescriptor.id);
|
||||||
if (asset.id === assetDescriptor.id) {
|
|
||||||
return asset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findClosest(target: TimelinePlainDateTime) {
|
findClosest(target: TimelinePlainDateTime) {
|
||||||
|
|||||||
Reference in New Issue
Block a user