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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ services:
- 2285:3001 - 2285:3001
redis: redis:
image: redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01 image: redis:6.2-alpine@sha256:2d1463258f2764328496376f5d965f20c6a67f66ea2b06dc42af351f75248792
database: database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0 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 Function(bool scrolling) scrollStateListener;
final double viewPortHeight;
DraggableScrollbar.semicircle({ DraggableScrollbar.semicircle({
super.key, super.key,
Key? scrollThumbKey, Key? scrollThumbKey,
@ -69,7 +67,6 @@ class DraggableScrollbar extends StatefulWidget {
required this.controller, required this.controller,
required this.itemPositionsListener, required this.itemPositionsListener,
required this.scrollStateListener, required this.scrollStateListener,
required this.viewPortHeight,
this.heightScrollThumb = 48.0, this.heightScrollThumb = 48.0,
this.backgroundColor = Colors.white, this.backgroundColor = Colors.white,
this.padding, this.padding,
@ -254,7 +251,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
} }
double get barMaxScrollExtent => double get barMaxScrollExtent =>
widget.viewPortHeight - (context.size?.height ?? 0) -
widget.heightScrollThumb - widget.heightScrollThumb -
(widget.heightOffset ?? 0); (widget.heightOffset ?? 0);
@ -340,8 +337,8 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
_thumbAnimationController.forward(); _thumbAnimationController.forward();
} }
if (itemPos < maxItemCount) { if (itemPosition < maxItemCount) {
_currentItem = itemPos; _currentItem = itemPosition;
} }
_fadeoutTimer?.cancel(); _fadeoutTimer?.cancel();
@ -365,25 +362,35 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
widget.scrollStateListener(true); widget.scrollStateListener(true);
} }
int get itemPos { int get itemPosition {
int numberOfItems = widget.child.itemCount; int numberOfItems = widget.child.itemCount;
return ((_barOffset / barMaxScrollExtent) * numberOfItems).toInt(); return ((_barOffset / barMaxScrollExtent) * numberOfItems).toInt();
} }
void _jumpToBarPos() { void _jumpToBarPosition() {
if (itemPos > maxItemCount - 1) { if (itemPosition > maxItemCount - 1) {
return; 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( widget.controller.jumpTo(
index: itemPos, index: itemPosition,
); );
} }
Timer? dragHaltTimer; Timer? dragHaltTimer;
int lastTimerPos = 0; int lastTimerPosition = 0;
void _onVerticalDragUpdate(DragUpdateDetails details) { void _onVerticalDragUpdate(DragUpdateDetails details) {
setState(() { setState(() {
@ -400,8 +407,8 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
_barOffset = barMaxScrollExtent; _barOffset = barMaxScrollExtent;
} }
if (itemPos != lastTimerPos) { if (itemPosition != lastTimerPosition) {
lastTimerPos = itemPos; lastTimerPosition = itemPosition;
dragHaltTimer?.cancel(); dragHaltTimer?.cancel();
widget.scrollStateListener(true); widget.scrollStateListener(true);
@ -413,7 +420,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
); );
} }
_jumpToBarPos(); _jumpToBarPosition();
} }
}); });
} }
@ -426,7 +433,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
}); });
setState(() { setState(() {
_jumpToBarPos(); _jumpToBarPosition();
_isDragInProcess = false; _isDragInProcess = false;
}); });

View File

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

View File

@ -237,7 +237,11 @@ class ImmichAppBarDialog extends HookConsumerWidget {
); );
} }
return Dialog( return Dismissible(
direction: DismissDirection.down,
onDismissed: (_) => Navigator.of(context).pop(),
key: const Key('app_bar_dialog'),
child: Dialog(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
insetPadding: EdgeInsets.only( insetPadding: EdgeInsets.only(
@ -269,6 +273,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
), ),
), ),
), ),
),
); );
} }
} }

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'); throw new BadRequestException('Live photo video not found');
} }
if (motionAsset.type !== AssetType.VIDEO) { 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) { if (motionAsset.ownerId !== auth.user.id) {
throw new BadRequestException('Live photo video does not belong to the user'); 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 () => { it('should extract hierarchy from HierarchicalSubject', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]); 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.parent);
tagMock.upsertValue.mockResolvedValueOnce(tagStub.child); tagMock.upsertValue.mockResolvedValueOnce(tagStub.child);
@ -465,6 +465,7 @@ describe(MetadataService.name, () => {
value: 'Parent/Child', value: 'Parent/Child',
parent: tagStub.parent, 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 () => { 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) { private async applyTagList(asset: AssetEntity, exifTags: ImmichTags) {
const tags: unknown[] = []; const tags: Array<string | number> = [];
if (exifTags.TagsList) { if (exifTags.TagsList) {
tags.push(...exifTags.TagsList); tags.push(...exifTags.TagsList);
} else if (exifTags.HierarchicalSubject) { } else if (exifTags.HierarchicalSubject) {
tags.push( tags.push(
exifTags.HierarchicalSubject.map((tag) => ...exifTags.HierarchicalSubject.map((tag) =>
tag tag
// convert | to / // convert | to /
.replaceAll('/', '<PLACEHOLDER>') .replaceAll('/', '<PLACEHOLDER>')

View File

@ -7,13 +7,14 @@
import { preferences, user } from '$lib/stores/user.store'; import { preferences, user } from '$lib/stores/user.store';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk'; 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 { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { NotificationType, notificationController } from '../notification/notification'; import { NotificationType, notificationController } from '../notification/notification';
import UserAvatar from '../user-avatar.svelte'; import UserAvatar from '../user-avatar.svelte';
import AvatarSelector from './avatar-selector.svelte'; import AvatarSelector from './avatar-selector.svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { page } from '$app/stores';
let isShowSelectAvatar = false; let isShowSelectAvatar = false;
@ -46,7 +47,7 @@
in:fade={{ duration: 100 }} in:fade={{ duration: 100 }}
out:fade={{ duration: 100 }} out:fade={{ duration: 100 }}
id="account-info-panel" 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 use:focusTrap
> >
<div <div
@ -73,6 +74,7 @@
<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{$user.email}</p> <p class="text-sm text-gray-500 dark:text-immich-dark-fg">{$user.email}</p>
</div> </div>
<div class="flex flex-col gap-1">
<Button <Button
href={AppRoute.USER_SETTINGS} href={AppRoute.USER_SETTINGS}
on:click={() => dispatch('close')} on:click={() => dispatch('close')}
@ -81,11 +83,28 @@
shadow={false} shadow={false}
border border
> >
<div class="flex place-content-center place-items-center gap-2 px-2"> <div class="flex place-content-center place-items-center text-center gap-2 px-2">
<Icon path={mdiCog} size="18" /> <Icon path={mdiCog} size="18" ariaHidden />
{$t('account_settings')} {$t('account_settings')}
</div> </div>
</Button> </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>
<div class="mb-4 flex flex-col"> <div class="mb-4 flex flex-col">

View File

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

View File

@ -16,7 +16,7 @@
$: hasQuota = $user?.quotaSizeInBytes !== null; $: hasQuota = $user?.quotaSizeInBytes !== null;
$: availableBytes = (hasQuota ? $user?.quotaSizeInBytes : $serverInfo?.diskSizeRaw) || 0; $: availableBytes = (hasQuota ? $user?.quotaSizeInBytes : $serverInfo?.diskSizeRaw) || 0;
$: usedBytes = (hasQuota ? $user?.quotaUsageInBytes : $serverInfo?.diskUseRaw) || 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; let aboutInfo: ServerAboutResponseDto;

View File

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