Compare commits

...

9 Commits

Author SHA1 Message Date
Alex
7fd0b41f30 fix(mobile): oauth login flow 2025-03-27 10:41:39 -05:00
Min Idzelis
c26b28f6a4 fix: bug with svelte gestures (#17154)
* fix: bug with svelte gestures

* lint
2025-03-27 08:51:52 -05:00
shenlong
c72c82c401 fix(mobile): faster device album refresh after initial sync (#17170)
fix(mobile): faster device album refresh after fresh sync

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-27 08:47:05 -05:00
Alex
fecf3809a6 fix(server): album count does not account for assets without exif (#17150)
* fix(server): album count doesn't accounted for assets without exif

* sql
2025-03-26 21:24:22 -05:00
Mert
619bd72de9 docs: mention rknn among image options (#17156)
mention rknn
2025-03-26 19:05:48 -04:00
Jason Rasmussen
fd4a5f71b5 fix: broken album page (#17149) 2025-03-26 18:59:23 -04:00
github-actions
2f8725c66f chore: version v1.130.2 2025-03-26 15:34:54 +00:00
Jonathan Jogenfors
9fbd6369b9 fix(server): check asset against multiple import paths (#17128)
* fix sql logic

* refactor: map import paths into not or sql statements

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-03-26 10:10:53 -05:00
Snowknight26
c547d849d9 fix(web): prevent comb box dropdowns from staying open when clicking on labels (#17119)
fix(web): prevent combobox dropdowns from staying open when clicking on label
2025-03-26 08:58:00 -05:00
28 changed files with 205 additions and 53 deletions

6
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.55", "version": "2.2.56",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.55", "version": "2.2.56",
"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",
@@ -55,7 +55,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.130.1", "version": "1.130.2",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.55", "version": "2.2.56",
"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",

View File

@@ -71,7 +71,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele
1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`. 1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`.
2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend. 2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend.
3. Still in `immich-machine-learning`, add one of -[armnn, cuda, rocm, openvino] to the `image` section's tag at the end of the line. 3. Still in `immich-machine-learning`, add one of -[armnn, cuda, rocm, openvino, rknn] to the `image` section's tag at the end of the line.
4. Redeploy the `immich-machine-learning` container with these updated settings. 4. Redeploy the `immich-machine-learning` container with these updated settings.
### Confirming Device Usage ### Confirming Device Usage

View File

@@ -23,12 +23,12 @@ name: immich_remote_ml
services: services:
immich-machine-learning: immich-machine-learning:
container_name: immich_machine_learning container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino] to the image tag. # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda # Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # extends:
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable # service: # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes: volumes:
- model-cache:/cache - model-cache:/cache
restart: always restart: always

View File

@@ -1,4 +1,8 @@
[ [
{
"label": "v1.130.2",
"url": "https://v1.130.2.archive.immich.app"
},
{ {
"label": "v1.130.1", "label": "v1.130.1",
"url": "https://v1.130.1.archive.immich.app" "url": "https://v1.130.1.archive.immich.app"

8
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.130.1", "version": "1.130.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.130.1", "version": "1.130.2",
"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",
@@ -45,7 +45,7 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.55", "version": "2.2.56",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -95,7 +95,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.130.1", "version": "1.130.2",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.130.1", "version": "1.130.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",

View File

@@ -454,6 +454,133 @@ describe('/libraries', () => {
utils.removeImageFile(`${testAssetDir}/temp/folder${char}2/asset2.png`); utils.removeImageFile(`${testAssetDir}/temp/folder${char}2/asset2.png`);
}); });
it('should respect exclusion patterns when using multiple import paths', async () => {
// https://github.com/immich-app/immich/issues/17121
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/exclusion/`, `${testAssetDirInternal}/temp/exclusion2/`],
});
const excludedFolder = `Raw`;
utils.createImageFile(`${testAssetDir}/temp/exclusion/asset1.png`);
utils.createImageFile(`${testAssetDir}/temp/exclusion/${excludedFolder}/asset2.png`);
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }),
expect.objectContaining({ originalPath: expect.stringContaining(`${excludedFolder}/asset2.png`) }),
]),
);
}
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }),
expect.objectContaining({ originalPath: expect.stringContaining(`${excludedFolder}/asset2.png`) }),
]),
);
}
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: [`**/${excludedFolder}/**`] });
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items).toEqual([
expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }),
]);
}
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items).toEqual([
expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }),
]);
}
utils.removeImageFile(`${testAssetDir}/temp/exclusion/asset1.png`);
utils.removeImageFile(`${testAssetDir}/temp/exclusion/${excludedFolder}/asset2.png`);
});
const annoyingExclusionPatterns = ['@', '#', '$', '%', '^', '&', '='];
it.each(annoyingExclusionPatterns)('should support exclusion patterns with %s', async (char) => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/exclusion/`],
});
const excludedFolder = `${char}folder`;
utils.createImageFile(`${testAssetDir}/temp/exclusion/asset1.png`);
utils.createImageFile(`${testAssetDir}/temp/exclusion/${excludedFolder}/asset2.png`);
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }),
expect.objectContaining({ originalPath: expect.stringContaining(`${excludedFolder}/asset2.png`) }),
]),
);
}
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }),
expect.objectContaining({ originalPath: expect.stringContaining(`${excludedFolder}/asset2.png`) }),
]),
);
}
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: [`**/${excludedFolder}/**`] });
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items).toEqual([
expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }),
]);
}
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items).toEqual([
expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }),
]);
}
utils.removeImageFile(`${testAssetDir}/temp/exclusion/asset1.png`);
utils.removeImageFile(`${testAssetDir}/temp/exclusion/${excludedFolder}/asset2.png`);
});
it('should reimport a modified file', async () => { it('should reimport a modified file', async () => {
const library = await utils.createLibrary(admin.accessToken, { const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId, ownerId: admin.userId,

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 189, "android.injected.version.code" => 190,
"android.injected.version.name" => "1.130.1", "android.injected.version.name" => "1.130.2",
} }
) )
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')

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Release" desc "iOS Release"
lane :release do lane :release do
increment_version_number( increment_version_number(
version_number: "1.130.1" version_number: "1.130.2"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,

View File

@@ -737,6 +737,11 @@ class SyncService {
album.thumbnail.value = thumb; album.thumbnail.value = thumb;
try { try {
await _albumRepository.create(album); await _albumRepository.create(album);
final int assetCount =
await _albumMediaRepository.getAssetCount(album.localId!);
await _eTagRepository.upsertAll([
ETag(id: album.eTagKeyAssetCount, assetCount: assetCount),
]);
_log.info("Added a new local album to DB: ${album.name}"); _log.info("Added a new local album to DB: ${album.name}");
} catch (e) { } catch (e) {
_log.severe("Failed to add new local album ${album.name} to DB", e); _log.severe("Failed to add new local album ${album.name} to DB", e);

View File

@@ -210,6 +210,9 @@ class LoginForm extends HookConsumerWidget {
.getOAuthServerUrl(sanitizeUrl(serverEndpointController.text)); .getOAuthServerUrl(sanitizeUrl(serverEndpointController.text));
isLoading.value = true; isLoading.value = true;
// Invalidate all api repository provider instance to take into account new access token
invalidateAllApiRepositoryProviders(ref);
} catch (error, stack) { } catch (error, stack) {
log.severe('Error getting OAuth server Url: $error', stack); log.severe('Error getting OAuth server Url: $error', stack);

View File

@@ -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.130.1 - API version: 1.130.2
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen

View File

@@ -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.130.1+189 version: 1.130.2+190
environment: environment:
sdk: '>=3.3.0 <4.0.0' sdk: '>=3.3.0 <4.0.0'

View File

@@ -7656,7 +7656,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "1.130.1", "version": "1.130.2",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],

View File

@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.130.1", "version": "1.130.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.130.1", "version": "1.130.2",
"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"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.130.1", "version": "1.130.2",
"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",

View File

@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 1.130.1 * 1.130.2
* 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
*/ */

View File

@@ -1,12 +1,12 @@
{ {
"name": "immich", "name": "immich",
"version": "1.130.1", "version": "1.130.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich", "name": "immich",
"version": "1.130.1", "version": "1.130.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@nestjs/bullmq": "^11.0.1", "@nestjs/bullmq": "^11.0.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "1.130.1", "version": "1.130.2",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,

View File

@@ -93,7 +93,7 @@ select
"exif" as "exifInfo" "exif" as "exifInfo"
from from
"assets" "assets"
inner join "exif" on "assets"."id" = "exif"."assetId" left join "exif" on "assets"."id" = "exif"."assetId"
inner join "albums_assets_assets" on "albums_assets_assets"."assetsId" = "assets"."id" inner join "albums_assets_assets" on "albums_assets_assets"."assetsId" = "assets"."id"
where where
"albums_assets_assets"."albumsId" = "albums"."id" "albums_assets_assets"."albumsId" = "albums"."id"

View File

@@ -68,7 +68,7 @@ const withAssets = (eb: ExpressionBuilder<DB, 'albums'>) => {
eb eb
.selectFrom('assets') .selectFrom('assets')
.selectAll('assets') .selectAll('assets')
.innerJoin('exif', 'assets.id', 'exif.assetId') .leftJoin('exif', 'assets.id', 'exif.assetId')
.select((eb) => eb.table('exif').as('exifInfo')) .select((eb) => eb.table('exif').as('exifInfo'))
.innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id') .innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
.whereRef('albums_assets_assets.albumsId', '=', 'albums.id') .whereRef('albums_assets_assets.albumsId', '=', 'albums.id')

View File

@@ -1062,7 +1062,10 @@ export class AssetRepository {
.where('isExternal', '=', true) .where('isExternal', '=', true)
.where('libraryId', '=', asUuid(libraryId)) .where('libraryId', '=', asUuid(libraryId))
.where((eb) => .where((eb) =>
eb.or([eb('originalPath', 'not like', paths.join('|')), eb('originalPath', 'like', exclusions.join('|'))]), eb.or([
eb.not(eb.or(paths.map((path) => eb('originalPath', 'like', path)))),
eb('originalPath', 'like', exclusions.join('|')),
]),
) )
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
} }

14
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.130.1", "version": "1.130.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-web", "name": "immich-web",
"version": "1.130.1", "version": "1.130.2",
"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",
@@ -70,7 +70,7 @@
"prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-sort-json": "^4.1.1",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"rollup-plugin-visualizer": "^5.14.0", "rollup-plugin-visualizer": "^5.14.0",
"svelte": "^5.22.6", "svelte": "^5.25.3",
"svelte-check": "^4.1.5", "svelte-check": "^4.1.5",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"tslib": "^2.6.2", "tslib": "^2.6.2",
@@ -81,7 +81,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.130.1", "version": "1.130.2",
"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"
@@ -8350,9 +8350,9 @@
} }
}, },
"node_modules/svelte": { "node_modules/svelte": {
"version": "5.23.0", "version": "5.25.3",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.23.0.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.25.3.tgz",
"integrity": "sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg==", "integrity": "sha512-J9rcZ/xVJonAoESqVGHHZhrNdVbrCfkdB41BP6eiwHMoFShD9it3yZXApVYMHdGfCshBsZCKsajwJeBbS/M1zg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.3.0", "@ampproject/remapping": "^2.3.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.130.1", "version": "1.130.2",
"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",
@@ -56,7 +56,7 @@
"prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-sort-json": "^4.1.1",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"rollup-plugin-visualizer": "^5.14.0", "rollup-plugin-visualizer": "^5.14.0",
"svelte": "^5.22.6", "svelte": "^5.25.3",
"svelte-check": "^4.1.5", "svelte-check": "^4.1.5",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"tslib": "^2.6.2", "tslib": "^2.6.2",

View File

@@ -1,6 +1,4 @@
<script lang="ts"> <script lang="ts">
import { press, tap } from 'svelte-gestures';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import { ProjectionType } from '$lib/constants'; import { ProjectionType } from '$lib/constants';
import { getAssetThumbnailUrl, isSharedLink } from '$lib/utils'; import { getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
@@ -103,10 +101,8 @@
} }
onClick?.($state.snapshot(asset)); onClick?.($state.snapshot(asset));
}; };
const handleClick = (e: MouseEvent) => { const handleClick = (e: MouseEvent) => {
if (isTouchDevice) {
return;
}
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
return; return;
} }
@@ -126,6 +122,25 @@
const onMouseLeave = () => { const onMouseLeave = () => {
mouseOver = false; mouseOver = false;
}; };
function longPress(element: HTMLElement, { onLongPress }: { onLongPress: () => void }) {
let timer: ReturnType<typeof setTimeout>;
const start = (event: TouchEvent) => {
timer = setTimeout(() => {
onLongPress();
event.preventDefault();
}, 350);
};
const end = () => clearTimeout(timer);
element.addEventListener('touchstart', start);
element.addEventListener('touchend', end);
return {
destroy: () => {
element.removeEventListener('touchstart', start);
element.removeEventListener('touchend', end);
},
};
}
</script> </script>
<div <div
@@ -146,9 +161,6 @@
></canvas> ></canvas>
{/if} {/if}
<!-- svelte queries for all links on afterNavigate, leading to performance problems in asset-grid which updates
the navigation url on scroll. Replace this with button for now. -->
<!-- as of iOS17, there is a preference for long press speed, which is not available for mobile web. <!-- as of iOS17, there is a preference for long press speed, which is not available for mobile web.
The defaults are as follows: The defaults are as follows:
fast: 200ms fast: 200ms
@@ -163,10 +175,7 @@
class:cursor-pointer={!disabled} class:cursor-pointer={!disabled}
onmouseenter={onMouseEnter} onmouseenter={onMouseEnter}
onmouseleave={onMouseLeave} onmouseleave={onMouseLeave}
use:press={() => ({ timeframe: 350, triggerBeforeFinished: true })} use:longPress={{ onLongPress: () => onSelect?.($state.snapshot(asset)) }}
use:tap={() => ({ timeframe: 350 })}
onpress={(evt) => (evt.detail.pointerType === 'mouse' ? void 0 : onSelect?.($state.snapshot(asset)))}
ontap={(evt) => (evt.detail.pointerType === 'mouse' ? void 0 : callClickHandlers())}
onkeydown={(evt) => { onkeydown={(evt) => {
if (evt.key === 'Enter') { if (evt.key === 'Enter') {
callClickHandlers(); callClickHandlers();

View File

@@ -1,4 +1,6 @@
<script lang="ts" module> <script lang="ts" module>
import { get } from 'svelte/store';
export type ComboBoxOption = { export type ComboBoxOption = {
id?: string; id?: string;
label: string; label: string;
@@ -28,7 +30,6 @@
import { generateId } from '$lib/utils/generate-id'; import { generateId } from '$lib/utils/generate-id';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { get } from 'svelte/store';
interface Props { interface Props {
label: string; label: string;
@@ -282,7 +283,6 @@
class:cursor-pointer={!isActive} class:cursor-pointer={!isActive}
class="immich-form-input text-sm text-left w-full !pr-12 transition-all" class="immich-form-input text-sm text-left w-full !pr-12 transition-all"
id={inputId} id={inputId}
onclick={activate}
onfocus={activate} onfocus={activate}
oninput={onInput} oninput={onInput}
role="combobox" role="combobox"
@@ -366,7 +366,7 @@
aria-disabled={true} aria-disabled={true}
class="text-left w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 cursor-default aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700" class="text-left w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 cursor-default aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700"
id={`${listboxId}-${0}`} id={`${listboxId}-${0}`}
onclick={() => closeDropdown()} onclick={closeDropdown}
> >
{allowCreate ? searchQuery : $t('no_results')} {allowCreate ? searchQuery : $t('no_results')}
</li> </li>

View File

@@ -394,7 +394,7 @@
} }
}); });
let album = $state(data.album); let album = $derived(data.album);
let albumId = $derived(album.id); let albumId = $derived(album.id);
$effect(() => { $effect(() => {
@@ -404,6 +404,7 @@
}); });
let assetStore = new AssetStore(); let assetStore = new AssetStore();
$effect(() => { $effect(() => {
if (viewMode === AlbumPageViewMode.VIEW) { if (viewMode === AlbumPageViewMode.VIEW) {
void assetStore.updateOptions({ albumId, order: albumOrder }); void assetStore.updateOptions({ albumId, order: albumOrder });