Merge branch 'main' of github.com:immich-app/immich into mobile/collections

This commit is contained in:
Alex 2024-09-09 14:19:42 -05:00
commit ad84a6e8c2
No known key found for this signature in database
GPG Key ID: 53CD082B3A5E1082
20 changed files with 203 additions and 191 deletions

View File

@ -43,7 +43,7 @@ jobs:
retag_ml:
name: Re-Tag ML
needs: pre-job
if: ${{ needs.pre-job.outputs.should_run_ml == 'false' }}
if: ${{ needs.pre-job.outputs.should_run_ml == 'false' && !github.event.pull_request.head.repo.fork }}
runs-on: ubuntu-latest
strategy:
matrix:
@ -51,8 +51,6 @@ jobs:
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
# Skip when PR from a fork
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -68,7 +66,7 @@ jobs:
retag_server:
name: Re-Tag Server
needs: pre-job
if: ${{ needs.pre-job.outputs.should_run_server == 'false' }}
if: ${{ needs.pre-job.outputs.should_run_server == 'false' && !github.event.pull_request.head.repo.fork }}
runs-on: ubuntu-latest
strategy:
matrix:
@ -76,8 +74,6 @@ jobs:
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
# Skip when PR from a fork
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}

View File

@ -2,37 +2,37 @@
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.40.0"
constraints = "4.40.0"
version = "4.41.0"
constraints = "4.41.0"
hashes = [
"h1:GP2N1tXrmpxu+qEDvFAmkfv9aeZNhag3bchyJpGpYbU=",
"h1:HDJKZBQkVU0kQl4gViQ5L7EcFLn9hB0iuvO+ORJiDS4=",
"h1:KrbeEsZoCJOnnX68yNI5h3QhMjc5bBCQW4yvYaEFq3s=",
"h1:LelwnzU0OVn6g2+T9Ub9XdpC+vbheraIL/qgXhWBs/k=",
"h1:TIq9CynfWrKgCxKL97Akj89cYlvJKn/AL4UXogd8/FM=",
"h1:Uoy5oPdm1ipDG7yIMCUN1IXMpsTGXahPw3I0rVA/6wA=",
"h1:Wunfpm+IZhENdoimrh4iXiakVnCsfKOHo80yJUjMQXM=",
"h1:cRdCuahMOFrNyldnCInqGQRBT1DTkRPSfPnaf5r05iw=",
"h1:k+zpXg8BO7gdbTIfSGyQisHhs5aVWQVbPLa5uUdr2UA=",
"h1:kWNrzZ8Rh0OpHikexkmwJIIucD6SMZPi4oGyDsKJitw=",
"h1:lomfTTjK78BdSEVTFcJUBQRy7IQHuGQImMaPWaYpfgQ=",
"h1:oWcWlZe52ZRyLQciNe94RaWzhHifSTu03nlK0uL7rlM=",
"h1:p3JJrhGEPlPQP7Uwy9FNMdvqCyD8tuT4lnXuJ+pSF/M=",
"h1:wtB0sKxG2K/H41hWJI4uJdImWquuaP34Sip5LmfE410=",
"zh:01742e5946f936548f8e42120287ffc757abf97e7cbbe34e25c266a438fb54fd",
"zh:08d81f5a5aab4cc269f983b8c6b5be0e278105136aca9681740802619577371f",
"zh:0d75131ba70902cfc94a7a5900369bdde56528b2aad6e10b164449cc97d57396",
"zh:3890a715a012e197541daacdacb8cceec6d364814daa4640ddfe98a8ba9036cb",
"zh:58254ce5ebe1faed4664df86210c39d660bcdc60280f17b25fe4d4dbea21ea8c",
"zh:6b0abc1adbc2edee79368ce9f7338ebcb5d0bf941e8d7d9ac505b750f20f80a2",
"zh:81cc415d1477174a1ca288d25fdb57e5ee488c2d7f61f265ef995b255a53b0ce",
"zh:8680140c7fe5beaefe61c5cfa471bf88422dc0c0f05dad6d3cb482d4ffd22be4",
"h1:0mc+YrjQrcctGrGYDmzlcqcgSv9MYB74rvMaZylIKC8=",
"h1:0zUx4vk4jOORQqn6xHBF7dO6N6bielFHdJ0mgF4Obn8=",
"h1:AsIZW3uLFNOZO7kL/K7/Y/S0IYxUV9Hz85NNk/3TTsA=",
"h1:FSgYM4+LHMbX/a4Y1kx7FPPWmXqS3/MQYzvjMJHHHWM=",
"h1:Tx6Nh3BWP1x9L3KK/Eyi+ET0T26g3+jf1jyiuqpNIis=",
"h1:VRI9wu8P43xxfpeTndRwsisLnqncfnmEYMOEH5zH4pQ=",
"h1:YxQqmiES/Yanq/VfGqBEqg+VIO7FGhO88aKoWFHyGIg=",
"h1:ZWHiaesjgDLKWlfdNj0oKyj/DWdxcfsO6NINu39zfpY=",
"h1:a2aCgDDBz3ccrr8YstIMl7VFnKo1xZAp+rOv59PPJ7U=",
"h1:aRyv8tB6wBAF9lKsLEdiHyCqnK5LfZq0FqMXCcUB4UU=",
"h1:lXpuO7zv2uD2GzPE1ARxznreRAh+QHTc2lAJ7iOoFgY=",
"h1:sA1xq0QNQ4fH8SHXouYNq50xirVD18SamKQwPsBQrrY=",
"h1:v7sHvKq7oqMYPn47ULHFyIQsKD9o+6Xg/uHbxQUixEw=",
"h1:wo/x4atWyXuWGlfR6h5nH0YwBAmBwTRY27HtWP8ycLo=",
"zh:339d26e06dc6fb299ea8aad9476a60fd65bb1d40631ae8eeb81cddf2dd2bebc8",
"zh:3dec2ad96ac2c283fd34ce65781b55c4edbb4d5c5cb53da8e31537176c0ed562",
"zh:5f63a5f8080319a2fff09d4d49944829fa708723436520787cfb60725ced80cf",
"zh:67162c28ccea71cb8141ed15c0637e35621354ebe14878e0b75a8f160fc5505d",
"zh:6ac1e07f5347b6395aca690ed22101bb25e957d25f986f760ff673a7adfd5ef6",
"zh:70282a723c7b52fcabde2baad41c864ed3a8d69f0c4d27a6b6933cac434cffc6",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:a491d26236122ccb83dac8cb490d2c0aa1f4d3a0b4abe99300fd49b1a624f42f",
"zh:a70d9c469dc8d55715ba77c9d1a4ede1fdebf79e60ee18438a0844868db54e0d",
"zh:a7fcb7d5c4222e14ec6d9a15adf8b9a083d84b102c3d0e4a0d102df5a1360b62",
"zh:b4f9677174fabd199c8ebd2e9e5eb3528cf887e700569a4fb61eef4e070cec5e",
"zh:c27f0f7519221d75dae4a3787a59e05acd5cc9a0d30a390eff349a77d20d52e6",
"zh:db00d8605dbf43ca42fe1481a6c67fdcaa73debb7d2a0f613cb95ae5c5e7150e",
"zh:924cd23abc326c6b3914e2cd9c94c7832c2552e1e9ae258fb9fd9aedaa5f7ce7",
"zh:a4b75e4c239879296259e7d54f1befbc7fdc16da2d62d1294e9f73add4cae61e",
"zh:a6ceb08feb63b00c7141783b31e45a154c76fd8cdebbdf371074805f0053572d",
"zh:afae1843f9ba85f2f6d94108c65cf43a457e83531a632d44d863e935160cb2ba",
"zh:bd6628ce60c778960a5755f7010b7e2cc5c6ff0341a21c175341b28058ec843d",
"zh:cd30866a1ff99d72b5fa1699db582fa4f25562e6ab21dcc6870324f3056108e0",
"zh:df5924cca691a8220aaaebb5cb55c3d6c32ff0a881f198695eff28155eb12b54",
"zh:e78d0696c941aba58df1cb36b8a0d25cd5f3963f01d9338fdbda74db58afdd49",
]
}

View File

@ -5,7 +5,7 @@ terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.40.0"
version = "4.41.0"
}
}
}

View File

@ -2,37 +2,37 @@
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.40.0"
constraints = "4.40.0"
version = "4.41.0"
constraints = "4.41.0"
hashes = [
"h1:GP2N1tXrmpxu+qEDvFAmkfv9aeZNhag3bchyJpGpYbU=",
"h1:HDJKZBQkVU0kQl4gViQ5L7EcFLn9hB0iuvO+ORJiDS4=",
"h1:KrbeEsZoCJOnnX68yNI5h3QhMjc5bBCQW4yvYaEFq3s=",
"h1:LelwnzU0OVn6g2+T9Ub9XdpC+vbheraIL/qgXhWBs/k=",
"h1:TIq9CynfWrKgCxKL97Akj89cYlvJKn/AL4UXogd8/FM=",
"h1:Uoy5oPdm1ipDG7yIMCUN1IXMpsTGXahPw3I0rVA/6wA=",
"h1:Wunfpm+IZhENdoimrh4iXiakVnCsfKOHo80yJUjMQXM=",
"h1:cRdCuahMOFrNyldnCInqGQRBT1DTkRPSfPnaf5r05iw=",
"h1:k+zpXg8BO7gdbTIfSGyQisHhs5aVWQVbPLa5uUdr2UA=",
"h1:kWNrzZ8Rh0OpHikexkmwJIIucD6SMZPi4oGyDsKJitw=",
"h1:lomfTTjK78BdSEVTFcJUBQRy7IQHuGQImMaPWaYpfgQ=",
"h1:oWcWlZe52ZRyLQciNe94RaWzhHifSTu03nlK0uL7rlM=",
"h1:p3JJrhGEPlPQP7Uwy9FNMdvqCyD8tuT4lnXuJ+pSF/M=",
"h1:wtB0sKxG2K/H41hWJI4uJdImWquuaP34Sip5LmfE410=",
"zh:01742e5946f936548f8e42120287ffc757abf97e7cbbe34e25c266a438fb54fd",
"zh:08d81f5a5aab4cc269f983b8c6b5be0e278105136aca9681740802619577371f",
"zh:0d75131ba70902cfc94a7a5900369bdde56528b2aad6e10b164449cc97d57396",
"zh:3890a715a012e197541daacdacb8cceec6d364814daa4640ddfe98a8ba9036cb",
"zh:58254ce5ebe1faed4664df86210c39d660bcdc60280f17b25fe4d4dbea21ea8c",
"zh:6b0abc1adbc2edee79368ce9f7338ebcb5d0bf941e8d7d9ac505b750f20f80a2",
"zh:81cc415d1477174a1ca288d25fdb57e5ee488c2d7f61f265ef995b255a53b0ce",
"zh:8680140c7fe5beaefe61c5cfa471bf88422dc0c0f05dad6d3cb482d4ffd22be4",
"h1:0mc+YrjQrcctGrGYDmzlcqcgSv9MYB74rvMaZylIKC8=",
"h1:0zUx4vk4jOORQqn6xHBF7dO6N6bielFHdJ0mgF4Obn8=",
"h1:AsIZW3uLFNOZO7kL/K7/Y/S0IYxUV9Hz85NNk/3TTsA=",
"h1:FSgYM4+LHMbX/a4Y1kx7FPPWmXqS3/MQYzvjMJHHHWM=",
"h1:Tx6Nh3BWP1x9L3KK/Eyi+ET0T26g3+jf1jyiuqpNIis=",
"h1:VRI9wu8P43xxfpeTndRwsisLnqncfnmEYMOEH5zH4pQ=",
"h1:YxQqmiES/Yanq/VfGqBEqg+VIO7FGhO88aKoWFHyGIg=",
"h1:ZWHiaesjgDLKWlfdNj0oKyj/DWdxcfsO6NINu39zfpY=",
"h1:a2aCgDDBz3ccrr8YstIMl7VFnKo1xZAp+rOv59PPJ7U=",
"h1:aRyv8tB6wBAF9lKsLEdiHyCqnK5LfZq0FqMXCcUB4UU=",
"h1:lXpuO7zv2uD2GzPE1ARxznreRAh+QHTc2lAJ7iOoFgY=",
"h1:sA1xq0QNQ4fH8SHXouYNq50xirVD18SamKQwPsBQrrY=",
"h1:v7sHvKq7oqMYPn47ULHFyIQsKD9o+6Xg/uHbxQUixEw=",
"h1:wo/x4atWyXuWGlfR6h5nH0YwBAmBwTRY27HtWP8ycLo=",
"zh:339d26e06dc6fb299ea8aad9476a60fd65bb1d40631ae8eeb81cddf2dd2bebc8",
"zh:3dec2ad96ac2c283fd34ce65781b55c4edbb4d5c5cb53da8e31537176c0ed562",
"zh:5f63a5f8080319a2fff09d4d49944829fa708723436520787cfb60725ced80cf",
"zh:67162c28ccea71cb8141ed15c0637e35621354ebe14878e0b75a8f160fc5505d",
"zh:6ac1e07f5347b6395aca690ed22101bb25e957d25f986f760ff673a7adfd5ef6",
"zh:70282a723c7b52fcabde2baad41c864ed3a8d69f0c4d27a6b6933cac434cffc6",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:a491d26236122ccb83dac8cb490d2c0aa1f4d3a0b4abe99300fd49b1a624f42f",
"zh:a70d9c469dc8d55715ba77c9d1a4ede1fdebf79e60ee18438a0844868db54e0d",
"zh:a7fcb7d5c4222e14ec6d9a15adf8b9a083d84b102c3d0e4a0d102df5a1360b62",
"zh:b4f9677174fabd199c8ebd2e9e5eb3528cf887e700569a4fb61eef4e070cec5e",
"zh:c27f0f7519221d75dae4a3787a59e05acd5cc9a0d30a390eff349a77d20d52e6",
"zh:db00d8605dbf43ca42fe1481a6c67fdcaa73debb7d2a0f613cb95ae5c5e7150e",
"zh:924cd23abc326c6b3914e2cd9c94c7832c2552e1e9ae258fb9fd9aedaa5f7ce7",
"zh:a4b75e4c239879296259e7d54f1befbc7fdc16da2d62d1294e9f73add4cae61e",
"zh:a6ceb08feb63b00c7141783b31e45a154c76fd8cdebbdf371074805f0053572d",
"zh:afae1843f9ba85f2f6d94108c65cf43a457e83531a632d44d863e935160cb2ba",
"zh:bd6628ce60c778960a5755f7010b7e2cc5c6ff0341a21c175341b28058ec843d",
"zh:cd30866a1ff99d72b5fa1699db582fa4f25562e6ab21dcc6870324f3056108e0",
"zh:df5924cca691a8220aaaebb5cb55c3d6c32ff0a881f198695eff28155eb12b54",
"zh:e78d0696c941aba58df1cb36b8a0d25cd5f3963f01d9338fdbda74db58afdd49",
]
}

View File

@ -5,7 +5,7 @@ terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.40.0"
version = "4.41.0"
}
}
}

View File

@ -98,7 +98,7 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01
image: redis:6.2-alpine@sha256:2d1463258f2764328496376f5d965f20c6a67f66ea2b06dc42af351f75248792
healthcheck:
test: redis-cli ping || exit 1

View File

@ -47,7 +47,7 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01
image: redis:6.2-alpine@sha256:2d1463258f2764328496376f5d965f20c6a67f66ea2b06dc42af351f75248792
healthcheck:
test: redis-cli ping || exit 1
restart: always

View File

@ -48,7 +48,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01
image: docker.io/redis:6.2-alpine@sha256:2d1463258f2764328496376f5d965f20c6a67f66ea2b06dc42af351f75248792
healthcheck:
test: redis-cli ping || exit 1
restart: always

View File

@ -33,7 +33,7 @@ services:
- 2285:3001
redis:
image: redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01
image: redis:6.2-alpine@sha256:2d1463258f2764328496376f5d965f20c6a67f66ea2b06dc42af351f75248792
database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0

View File

@ -59,8 +59,6 @@ class DraggableScrollbar extends StatefulWidget {
final Function(bool scrolling) scrollStateListener;
final double viewPortHeight;
DraggableScrollbar.semicircle({
super.key,
Key? scrollThumbKey,
@ -69,7 +67,6 @@ class DraggableScrollbar extends StatefulWidget {
required this.controller,
required this.itemPositionsListener,
required this.scrollStateListener,
required this.viewPortHeight,
this.heightScrollThumb = 48.0,
this.backgroundColor = Colors.white,
this.padding,
@ -254,7 +251,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
}
double get barMaxScrollExtent =>
widget.viewPortHeight -
(context.size?.height ?? 0) -
widget.heightScrollThumb -
(widget.heightOffset ?? 0);
@ -340,8 +337,8 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
_thumbAnimationController.forward();
}
if (itemPos < maxItemCount) {
_currentItem = itemPos;
if (itemPosition < maxItemCount) {
_currentItem = itemPosition;
}
_fadeoutTimer?.cancel();
@ -365,25 +362,35 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
widget.scrollStateListener(true);
}
int get itemPos {
int get itemPosition {
int numberOfItems = widget.child.itemCount;
return ((_barOffset / barMaxScrollExtent) * numberOfItems).toInt();
}
void _jumpToBarPos() {
if (itemPos > maxItemCount - 1) {
void _jumpToBarPosition() {
if (itemPosition > maxItemCount - 1) {
return;
}
_currentItem = itemPos;
_currentItem = itemPosition;
/// If the bar is at the bottom but the item position is still smaller than the max item count (due to rounding error)
/// jump to the end of the list
if (barMaxScrollExtent - _barOffset < 10 && itemPosition < maxItemCount) {
widget.controller.jumpTo(
index: maxItemCount,
);
return;
}
widget.controller.jumpTo(
index: itemPos,
index: itemPosition,
);
}
Timer? dragHaltTimer;
int lastTimerPos = 0;
int lastTimerPosition = 0;
void _onVerticalDragUpdate(DragUpdateDetails details) {
setState(() {
@ -400,8 +407,8 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
_barOffset = barMaxScrollExtent;
}
if (itemPos != lastTimerPos) {
lastTimerPos = itemPos;
if (itemPosition != lastTimerPosition) {
lastTimerPosition = itemPosition;
dragHaltTimer?.cancel();
widget.scrollStateListener(true);
@ -413,7 +420,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
);
}
_jumpToBarPos();
_jumpToBarPosition();
}
});
}
@ -426,7 +433,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
});
setState(() {
_jumpToBarPos();
_jumpToBarPosition();
_isDragInProcess = false;
});

View File

@ -264,7 +264,6 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
final child = (useDragScrolling && ModalRoute.of(context) != null)
? DraggableScrollbar.semicircle(
viewPortHeight: context.height,
scrollStateListener: dragScrolling,
itemPositionsListener: _itemPositionsListener,
controller: _itemScrollController,

View File

@ -237,35 +237,40 @@ class ImmichAppBarDialog extends HookConsumerWidget {
);
}
return Dialog(
clipBehavior: Clip.hardEdge,
alignment: Alignment.topCenter,
insetPadding: EdgeInsets.only(
top: isHorizontal ? 20 : 40,
left: horizontalPadding,
right: horizontalPadding,
bottom: isHorizontal ? 20 : 100,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: SizedBox(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(20),
child: buildTopRow(),
),
const AppBarProfileInfoBox(),
buildStorageInformation(),
const AppBarServerInfo(),
buildAppLogButton(),
buildSettingButton(),
buildSignOutButton(),
buildFooter(),
],
return Dismissible(
direction: DismissDirection.down,
onDismissed: (_) => Navigator.of(context).pop(),
key: const Key('app_bar_dialog'),
child: Dialog(
clipBehavior: Clip.hardEdge,
alignment: Alignment.topCenter,
insetPadding: EdgeInsets.only(
top: isHorizontal ? 20 : 40,
left: horizontalPadding,
right: horizontalPadding,
bottom: isHorizontal ? 20 : 100,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: SizedBox(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(20),
child: buildTopRow(),
),
const AppBarProfileInfoBox(),
buildStorageInformation(),
const AppBarServerInfo(),
buildAppLogButton(),
buildSettingButton(),
buildSignOutButton(),
buildFooter(),
],
),
),
),
),

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class RemoveHiddenAssetsFromAlbums1725730782681 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DELETE FROM "albums_assets_assets" WHERE "assetsId" IN (SELECT "id" FROM "assets" WHERE "isVisible" = false)`,
);
}
public async down(): Promise<void> {
// noop
}
}

View File

@ -163,7 +163,7 @@ export class AssetMediaService {
throw new BadRequestException('Live photo video not found');
}
if (motionAsset.type !== AssetType.VIDEO) {
throw new BadRequestException('Live photo vide must be a video');
throw new BadRequestException('Live photo video must be a video');
}
if (motionAsset.ownerId !== auth.user.id) {
throw new BadRequestException('Live photo video does not belong to the user');

View File

@ -453,7 +453,7 @@ describe(MetadataService.name, () => {
it('should extract hierarchy from HierarchicalSubject', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Parent|Child'] });
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Parent|Child', 'TagA'] });
tagMock.upsertValue.mockResolvedValueOnce(tagStub.parent);
tagMock.upsertValue.mockResolvedValueOnce(tagStub.child);
@ -465,6 +465,7 @@ describe(MetadataService.name, () => {
value: 'Parent/Child',
parent: tagStub.parent,
});
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(3, { userId: 'user-id', value: 'TagA', parent: undefined });
});
it('should extract ignore / characters in a HierarchicalSubject tag', async () => {

View File

@ -384,12 +384,12 @@ export class MetadataService {
}
private async applyTagList(asset: AssetEntity, exifTags: ImmichTags) {
const tags: unknown[] = [];
const tags: Array<string | number> = [];
if (exifTags.TagsList) {
tags.push(...exifTags.TagsList);
} else if (exifTags.HierarchicalSubject) {
tags.push(
exifTags.HierarchicalSubject.map((tag) =>
...exifTags.HierarchicalSubject.map((tag) =>
tag
// convert | to /
.replaceAll('/', '<PLACEHOLDER>')

View File

@ -7,13 +7,14 @@
import { preferences, user } from '$lib/stores/user.store';
import { handleError } from '$lib/utils/handle-error';
import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk';
import { mdiCog, mdiLogout, mdiPencil } from '@mdi/js';
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import { NotificationType, notificationController } from '../notification/notification';
import UserAvatar from '../user-avatar.svelte';
import AvatarSelector from './avatar-selector.svelte';
import { t } from 'svelte-i18n';
import { page } from '$app/stores';
let isShowSelectAvatar = false;
@ -46,7 +47,7 @@
in:fade={{ duration: 100 }}
out:fade={{ duration: 100 }}
id="account-info-panel"
class="absolute right-[25px] top-[75px] z-[100] w-[360px] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
class="absolute right-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
use:focusTrap
>
<div
@ -73,19 +74,37 @@
<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{$user.email}</p>
</div>
<Button
href={AppRoute.USER_SETTINGS}
on:click={() => dispatch('close')}
color="dark-gray"
size="sm"
shadow={false}
border
>
<div class="flex place-content-center place-items-center gap-2 px-2">
<Icon path={mdiCog} size="18" />
{$t('account_settings')}
</div>
</Button>
<div class="flex flex-col gap-1">
<Button
href={AppRoute.USER_SETTINGS}
on:click={() => dispatch('close')}
color="dark-gray"
size="sm"
shadow={false}
border
>
<div class="flex place-content-center place-items-center text-center gap-2 px-2">
<Icon path={mdiCog} size="18" ariaHidden />
{$t('account_settings')}
</div>
</Button>
{#if $user.isAdmin}
<Button
href={AppRoute.ADMIN_USER_MANAGEMENT}
on:click={() => dispatch('close')}
color="dark-gray"
size="sm"
shadow={false}
border
aria-current={$page.url.pathname.includes('/admin') ? 'page' : undefined}
>
<div class="flex place-content-center place-items-center text-center gap-2 px-2">
<Icon path={mdiWrench} size="18" ariaHidden />
{$t('administration')}
</div>
</Button>
{/if}
</div>
</div>
<div class="mb-4 flex flex-col">

View File

@ -9,10 +9,10 @@
import { user } from '$lib/stores/user.store';
import { handleLogout } from '$lib/utils/auth';
import { logout } from '@immich/sdk';
import { mdiCog, mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
import { mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import { fade, fly } from 'svelte/transition';
import { fade } from 'svelte/transition';
import { AppRoute } from '../../../constants';
import ImmichLogo from '../immich-logo.svelte';
import SearchBar from '../search-bar/search-bar.svelte';
@ -45,72 +45,41 @@
<a data-sveltekit-preload-data="hover" class="ml-4" href={AppRoute.PHOTOS}>
<ImmichLogo width="55%" noText={innerWidth < 768} />
</a>
<div class="flex justify-between gap-16 pr-6">
<div class="hidden w-full max-w-5xl flex-1 pl-4 tall:pl-0 sm:block">
<div class="flex justify-between gap-4 lg:gap-8 pr-6">
<div class="hidden w-full max-w-5xl flex-1 tall:pl-0 sm:block">
{#if $featureFlags.search}
<SearchBar grayTheme={true} />
{/if}
</div>
<section class="flex place-items-center justify-end gap-4 max-sm:w-full">
<section class="flex place-items-center justify-end gap-2 md:gap-4 w-full sm:w-auto">
{#if $featureFlags.search}
<CircleIconButton
href={AppRoute.SEARCH}
id="search-button"
class="ml-4 sm:hidden"
class="sm:hidden"
title={$t('go_to_search')}
icon={mdiMagnify}
padding="2"
/>
{/if}
<ThemeButton />
<ThemeButton padding="2" />
{#if !$page.url.pathname.includes('/admin') && showUploadButton}
<div in:fly={{ x: 50, duration: 250 }}>
<LinkButton on:click={() => dispatch('uploadClicked')}>
<div class="flex gap-2">
<Icon path={mdiTrayArrowUp} size="1.5em" />
<span class="hidden md:block">{$t('upload')}</span>
</div>
</LinkButton>
</div>
{/if}
{#if $user.isAdmin}
<a
data-sveltekit-preload-data="hover"
href={AppRoute.ADMIN_USER_MANAGEMENT}
aria-label={$t('administration')}
aria-current={$page.url.pathname.includes('/admin') ? 'page' : null}
>
<div
class="inline-flex items-center justify-center transition-colors dark:text-immich-dark-fg p-2 font-medium rounded-lg"
>
<div class="hidden sm:block">
<span
class={$page.url.pathname.includes('/admin')
? 'item text-immich-primary underline dark:text-immich-dark-primary'
: ''}
>
{$t('administration')}
</span>
</div>
<div class="block sm:hidden" aria-hidden="true">
<Icon
path={mdiCog}
size="1.5em"
class="dark:text-immich-dark-fg {$page.url.pathname.includes('/admin')
? 'text-immich-primary dark:text-immich-dark-primary'
: ''}"
/>
<div
class={$page.url.pathname.includes('/admin')
? 'border-t-1 mx-auto block w-2/3 border-immich-primary dark:border-immich-dark-primary'
: 'hidden'}
/>
</div>
<LinkButton on:click={() => dispatch('uploadClicked')} class="hidden lg:block">
<div class="flex gap-2">
<Icon path={mdiTrayArrowUp} size="1.5em" />
<span>{$t('upload')}</span>
</div>
</a>
</LinkButton>
<CircleIconButton
on:click={() => dispatch('uploadClicked')}
title={$t('upload')}
icon={mdiTrayArrowUp}
class="lg:hidden"
padding="2"
/>
{/if}
<div
@ -129,7 +98,7 @@
on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)}
>
{#key $user}
<UserAvatar user={$user} size="lg" showTitle={false} interactive />
<UserAvatar user={$user} size="md" showTitle={false} interactive />
{/key}
</button>

View File

@ -16,7 +16,7 @@
$: hasQuota = $user?.quotaSizeInBytes !== null;
$: availableBytes = (hasQuota ? $user?.quotaSizeInBytes : $serverInfo?.diskSizeRaw) || 0;
$: usedBytes = (hasQuota ? $user?.quotaUsageInBytes : $serverInfo?.diskUseRaw) || 0;
$: usedPercentage = Math.round((usedBytes / availableBytes) * 100);
$: usedPercentage = Math.min(Math.round((usedBytes / availableBytes) * 100), 100);
let aboutInfo: ServerAboutResponseDto;

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { moonPath, moonViewBox, sunPath, sunViewBox } from '$lib/assets/svg-paths';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import CircleIconButton, { type Padding } from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { Theme } from '$lib/constants';
import { colorTheme, handleToggleTheme } from '$lib/stores/preferences.store';
import { t } from 'svelte-i18n';
@ -8,6 +8,8 @@
$: icon = $colorTheme.value === Theme.LIGHT ? moonPath : sunPath;
$: viewBox = $colorTheme.value === Theme.LIGHT ? moonViewBox : sunViewBox;
$: isDark = $colorTheme.value === Theme.DARK;
export let padding: Padding = '3';
</script>
{#if !$colorTheme.system}
@ -18,5 +20,6 @@
role="switch"
aria-checked={isDark ? 'true' : 'false'}
on:click={handleToggleTheme}
{padding}
/>
{/if}