Compare commits
9 Commits
revert-res
...
fix-17167
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fd0b41f30 | ||
|
|
c26b28f6a4 | ||
|
|
c72c82c401 | ||
|
|
fecf3809a6 | ||
|
|
619bd72de9 | ||
|
|
fd4a5f71b5 | ||
|
|
2f8725c66f | ||
|
|
9fbd6369b9 | ||
|
|
c547d849d9 |
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.55",
|
||||
"version": "2.2.56",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.55",
|
||||
"version": "2.2.56",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
@@ -55,7 +55,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.55",
|
||||
"version": "2.2.56",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
|
||||
@@ -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`.
|
||||
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.
|
||||
|
||||
### Confirming Device Usage
|
||||
|
||||
@@ -23,12 +23,12 @@ name: immich_remote_ml
|
||||
services:
|
||||
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
|
||||
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
|
||||
# extends:
|
||||
# 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:
|
||||
- model-cache:/cache
|
||||
restart: always
|
||||
|
||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,4 +1,8 @@
|
||||
[
|
||||
{
|
||||
"label": "v1.130.2",
|
||||
"url": "https://v1.130.2.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v1.130.1",
|
||||
"url": "https://v1.130.1.archive.immich.app"
|
||||
|
||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-e2e",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"../cli": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.55",
|
||||
"version": "2.2.56",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
@@ -95,7 +95,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -454,6 +454,133 @@ describe('/libraries', () => {
|
||||
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 () => {
|
||||
const library = await utils.createLibrary(admin.accessToken, {
|
||||
ownerId: admin.userId,
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 189,
|
||||
"android.injected.version.name" => "1.130.1",
|
||||
"android.injected.version.code" => 190,
|
||||
"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')
|
||||
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Release"
|
||||
lane :release do
|
||||
increment_version_number(
|
||||
version_number: "1.130.1"
|
||||
version_number: "1.130.2"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -737,6 +737,11 @@ class SyncService {
|
||||
album.thumbnail.value = thumb;
|
||||
try {
|
||||
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}");
|
||||
} catch (e) {
|
||||
_log.severe("Failed to add new local album ${album.name} to DB", e);
|
||||
|
||||
@@ -210,6 +210,9 @@ class LoginForm extends HookConsumerWidget {
|
||||
.getOAuthServerUrl(sanitizeUrl(serverEndpointController.text));
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
// Invalidate all api repository provider instance to take into account new access token
|
||||
invalidateAllApiRepositoryProviders(ref);
|
||||
} catch (error, stack) {
|
||||
log.severe('Error getting OAuth server Url: $error', stack);
|
||||
|
||||
|
||||
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:
|
||||
|
||||
- API version: 1.130.1
|
||||
- API version: 1.130.2
|
||||
- Generator version: 7.8.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: 'none'
|
||||
version: 1.130.1+189
|
||||
version: 1.130.2+190
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.0 <4.0.0'
|
||||
|
||||
@@ -7656,7 +7656,7 @@
|
||||
"info": {
|
||||
"title": "Immich",
|
||||
"description": "Immich API",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"contact": {}
|
||||
},
|
||||
"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",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||
"type": "module",
|
||||
"main": "./build/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Immich
|
||||
* 1.130.1
|
||||
* 1.130.2
|
||||
* DO NOT MODIFY - This file has been generated using 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",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@nestjs/bullmq": "^11.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -93,7 +93,7 @@ select
|
||||
"exif" as "exifInfo"
|
||||
from
|
||||
"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"
|
||||
where
|
||||
"albums_assets_assets"."albumsId" = "albums"."id"
|
||||
|
||||
@@ -68,7 +68,7 @@ const withAssets = (eb: ExpressionBuilder<DB, 'albums'>) => {
|
||||
eb
|
||||
.selectFrom('assets')
|
||||
.selectAll('assets')
|
||||
.innerJoin('exif', 'assets.id', 'exif.assetId')
|
||||
.leftJoin('exif', 'assets.id', 'exif.assetId')
|
||||
.select((eb) => eb.table('exif').as('exifInfo'))
|
||||
.innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
|
||||
.whereRef('albums_assets_assets.albumsId', '=', 'albums.id')
|
||||
|
||||
@@ -1062,7 +1062,10 @@ export class AssetRepository {
|
||||
.where('isExternal', '=', true)
|
||||
.where('libraryId', '=', asUuid(libraryId))
|
||||
.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();
|
||||
}
|
||||
|
||||
14
web/package-lock.json
generated
14
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-web",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
@@ -70,7 +70,7 @@
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"svelte": "^5.22.6",
|
||||
"svelte": "^5.25.3",
|
||||
"svelte-check": "^4.1.5",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tslib": "^2.6.2",
|
||||
@@ -81,7 +81,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
@@ -8350,9 +8350,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.23.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.23.0.tgz",
|
||||
"integrity": "sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg==",
|
||||
"version": "5.25.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.25.3.tgz",
|
||||
"integrity": "sha512-J9rcZ/xVJonAoESqVGHHZhrNdVbrCfkdB41BP6eiwHMoFShD9it3yZXApVYMHdGfCshBsZCKsajwJeBbS/M1zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.130.1",
|
||||
"version": "1.130.2",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"scripts": {
|
||||
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
||||
@@ -56,7 +56,7 @@
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"svelte": "^5.22.6",
|
||||
"svelte": "^5.25.3",
|
||||
"svelte-check": "^4.1.5",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tslib": "^2.6.2",
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { press, tap } from 'svelte-gestures';
|
||||
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { ProjectionType } from '$lib/constants';
|
||||
import { getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||
@@ -103,10 +101,8 @@
|
||||
}
|
||||
onClick?.($state.snapshot(asset));
|
||||
};
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (isTouchDevice) {
|
||||
return;
|
||||
}
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
return;
|
||||
}
|
||||
@@ -126,6 +122,25 @@
|
||||
const onMouseLeave = () => {
|
||||
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>
|
||||
|
||||
<div
|
||||
@@ -146,9 +161,6 @@
|
||||
></canvas>
|
||||
{/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.
|
||||
The defaults are as follows:
|
||||
fast: 200ms
|
||||
@@ -163,10 +175,7 @@
|
||||
class:cursor-pointer={!disabled}
|
||||
onmouseenter={onMouseEnter}
|
||||
onmouseleave={onMouseLeave}
|
||||
use:press={() => ({ timeframe: 350, triggerBeforeFinished: true })}
|
||||
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())}
|
||||
use:longPress={{ onLongPress: () => onSelect?.($state.snapshot(asset)) }}
|
||||
onkeydown={(evt) => {
|
||||
if (evt.key === 'Enter') {
|
||||
callClickHandlers();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" module>
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export type ComboBoxOption = {
|
||||
id?: string;
|
||||
label: string;
|
||||
@@ -28,7 +30,6 @@
|
||||
import { generateId } from '$lib/utils/generate-id';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
@@ -282,7 +283,6 @@
|
||||
class:cursor-pointer={!isActive}
|
||||
class="immich-form-input text-sm text-left w-full !pr-12 transition-all"
|
||||
id={inputId}
|
||||
onclick={activate}
|
||||
onfocus={activate}
|
||||
oninput={onInput}
|
||||
role="combobox"
|
||||
@@ -366,7 +366,7 @@
|
||||
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"
|
||||
id={`${listboxId}-${0}`}
|
||||
onclick={() => closeDropdown()}
|
||||
onclick={closeDropdown}
|
||||
>
|
||||
{allowCreate ? searchQuery : $t('no_results')}
|
||||
</li>
|
||||
|
||||
@@ -63,11 +63,14 @@
|
||||
const _assets = assets;
|
||||
updateSlidingWindow();
|
||||
|
||||
const rowWidth = Math.floor(viewport.width);
|
||||
const rowHeight = rowWidth < 850 ? 100 : 235;
|
||||
|
||||
geometry = getJustifiedLayoutFromAssets(_assets, {
|
||||
spacing: 2,
|
||||
heightTolerance: 0.15,
|
||||
rowHeight: 235,
|
||||
rowWidth: Math.floor(viewport.width),
|
||||
rowHeight,
|
||||
rowWidth,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ export type AssetStoreOptions = Omit<AssetApiGetTimeBucketsRequest, 'size'> & {
|
||||
timelineAlbumId?: string;
|
||||
deferInit?: boolean;
|
||||
};
|
||||
|
||||
export type AssetStoreLayoutOptions = {
|
||||
rowHeight: number;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function updateObject(target: any, source: any): boolean {
|
||||
if (!target) {
|
||||
@@ -576,6 +578,7 @@ export class AssetStore {
|
||||
|
||||
// --- private
|
||||
static #INIT_OPTIONS = {};
|
||||
#rowHeight = 235;
|
||||
#viewportHeight = $state(0);
|
||||
#viewportWidth = $state(0);
|
||||
#scrollTop = $state(0);
|
||||
@@ -618,6 +621,7 @@ export class AssetStore {
|
||||
const changed = value !== this.#viewportWidth;
|
||||
this.#viewportWidth = value;
|
||||
this.suspendTransitions = true;
|
||||
this.#rowHeight = value < 850 ? 100 : 235;
|
||||
// side-effect - its ok!
|
||||
void this.#updateViewportGeometry(changed);
|
||||
}
|
||||
@@ -793,6 +797,11 @@ export class AssetStore {
|
||||
this.#updateViewportGeometry(false);
|
||||
}
|
||||
|
||||
updateLayoutOptions(options: AssetStoreLayoutOptions) {
|
||||
this.#rowHeight = options.rowHeight;
|
||||
this.refreshLayout();
|
||||
}
|
||||
|
||||
async #init(options: AssetStoreOptions) {
|
||||
// doing the following outside of the task reduces flickr
|
||||
this.isInitialized = false;
|
||||
@@ -862,10 +871,11 @@ export class AssetStore {
|
||||
|
||||
createLayoutOptions() {
|
||||
const viewportWidth = this.viewportWidth;
|
||||
|
||||
return {
|
||||
spacing: 2,
|
||||
heightTolerance: 0.15,
|
||||
rowHeight: 235,
|
||||
rowHeight: this.#rowHeight,
|
||||
rowWidth: Math.floor(viewportWidth),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
let album = $state(data.album);
|
||||
let album = $derived(data.album);
|
||||
let albumId = $derived(album.id);
|
||||
|
||||
$effect(() => {
|
||||
@@ -404,6 +404,7 @@
|
||||
});
|
||||
|
||||
let assetStore = new AssetStore();
|
||||
|
||||
$effect(() => {
|
||||
if (viewMode === AlbumPageViewMode.VIEW) {
|
||||
void assetStore.updateOptions({ albumId, order: albumOrder });
|
||||
|
||||
Reference in New Issue
Block a user