mirror of
https://github.com/immich-app/immich.git
synced 2026-06-13 03:21:45 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 439b7fe3be | |||
| b430f5188b | |||
| 54c1fbebde |
@@ -15,7 +15,7 @@ jobs:
|
||||
outputs:
|
||||
uses_template: ${{ steps.check.outputs.uses_template }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: .github/pull_request_template.md
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
@@ -211,7 +211,7 @@ jobs:
|
||||
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -20,12 +20,12 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check for breaking API changes
|
||||
uses: oasdiff/oasdiff-action/breaking@a8c7f0e5649d20d623edb5b38446d3ab3d82d43c # v0.0.53
|
||||
uses: oasdiff/oasdiff-action/breaking@50e6a3413e5aa9c3ae4d8393c34745be44288b46 # v0.0.48
|
||||
with:
|
||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||
revision: open-api/immich-openapi-specs.json
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -50,14 +50,14 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -83,6 +83,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
persist-credentials: true
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
persist-credentials: true
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
# TODO move to mise
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
|
||||
- name: Bump version
|
||||
env:
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
persist-credentials: false
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
+17
-17
@@ -81,7 +81,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -140,7 +140,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -171,7 +171,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -253,7 +253,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -281,7 +281,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -331,7 +331,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -363,7 +363,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -399,7 +399,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -476,7 +476,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: 'recursive'
|
||||
@@ -583,7 +583,7 @@ jobs:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -621,7 +621,7 @@ jobs:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -652,7 +652,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -681,7 +681,7 @@ jobs:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -703,7 +703,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
@@ -761,7 +761,7 @@ jobs:
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
@@ -23,7 +23,7 @@ class MapBottomSheet extends StatelessWidget {
|
||||
resizeOnScroll: false,
|
||||
actions: [],
|
||||
backgroundColor: context.themeData.colorScheme.surface,
|
||||
slivers: [const SliverFillRemaining(hasScrollBody: true, child: _ScopedMapTimeline())],
|
||||
slivers: [const SliverFillRemaining(hasScrollBody: false, child: _ScopedMapTimeline())],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet
|
||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||
import 'package:immich_mobile/utils/debounce.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
@@ -132,7 +133,8 @@ class _DriftMapState extends ConsumerState<DriftMap> {
|
||||
// When the AssetViewer is open, the DriftMap route stays alive in the background.
|
||||
// If we continue to update bounds, the map-scoped timeline service gets recreated and the previous one disposed,
|
||||
// which can invalidate the TimelineService instance that was passed into AssetViewerRoute (causing "loading forever").
|
||||
if (ref.read(isAssetViewerOpenProvider)) {
|
||||
final currentRoute = ref.read(currentRouteNameProvider);
|
||||
if (currentRoute == AssetViewerRoute.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,11 +183,6 @@ class _DriftMapState extends ConsumerState<DriftMap> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen<bool>(isAssetViewerOpenProvider, (previous, current) {
|
||||
if (previous == true && !current) {
|
||||
_debouncer.run(() => setBounds(forceReload: true));
|
||||
}
|
||||
});
|
||||
return Stack(
|
||||
children: [
|
||||
_Map(initialLocation: widget.initialLocation, onMapCreated: onMapCreated, onMapReady: onMapReady),
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
final inLockedViewProvider = StateProvider<bool>((ref) => false);
|
||||
final isAssetViewerOpenProvider = StateProvider<bool>((ref) => false);
|
||||
final currentRouteNameProvider = StateProvider<String?>((ref) => null);
|
||||
final previousRouteNameProvider = StateProvider<String?>((ref) => null);
|
||||
final previousRouteDataProvider = StateProvider<RouteSettings?>((ref) => null);
|
||||
|
||||
@@ -24,20 +24,9 @@ class AppNavigationObserver extends AutoRouterObserver {
|
||||
ref.read(currentRouteNameProvider.notifier).state = route.settings.name;
|
||||
ref.read(previousRouteNameProvider.notifier).state = previousRoute?.settings.name;
|
||||
ref.read(previousRouteDataProvider.notifier).state = previousRoute?.settings;
|
||||
if (route.settings.name == AssetViewerRoute.name) {
|
||||
ref.read(isAssetViewerOpenProvider.notifier).state = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didPop(Route route, Route? previousRoute) {
|
||||
_handleDriftLockedFolderState(previousRoute ?? route, null);
|
||||
if (route.settings.name == AssetViewerRoute.name) {
|
||||
Future(() => ref.read(isAssetViewerOpenProvider.notifier).state = false);
|
||||
}
|
||||
}
|
||||
|
||||
_handleDriftLockedFolderState(Route route, Route? previousRoute) {
|
||||
final isInLockedView = ref.read(inLockedViewProvider);
|
||||
final isFromLockedViewToDetailView =
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ class AlbumResponseDto {
|
||||
/// Album description
|
||||
String description;
|
||||
|
||||
/// End date (latest asset)
|
||||
/// UTC representation of (local) end date (latest asset)
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -92,7 +92,7 @@ class AlbumResponseDto {
|
||||
/// Is shared album
|
||||
bool shared;
|
||||
|
||||
/// Start date (earliest asset)
|
||||
/// UTC representation of (local) start date (earliest asset)
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
@@ -16235,7 +16235,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"endDate": {
|
||||
"description": "End date (latest asset)",
|
||||
"description": "UTC representation of (local) end date (latest asset)",
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16264,7 +16264,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"startDate": {
|
||||
"description": "Start date (earliest asset)",
|
||||
"description": "UTC representation of (local) start date (earliest asset)",
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -484,7 +484,7 @@ export type AlbumResponseDto = {
|
||||
createdAt: string;
|
||||
/** Album description */
|
||||
description: string;
|
||||
/** End date (latest asset) */
|
||||
/** UTC representation of (local) end date (latest asset) */
|
||||
endDate?: string;
|
||||
/** Has shared link */
|
||||
hasSharedLink: boolean;
|
||||
@@ -497,7 +497,7 @@ export type AlbumResponseDto = {
|
||||
order?: AssetOrder;
|
||||
/** Is shared album */
|
||||
shared: boolean;
|
||||
/** Start date (earliest asset) */
|
||||
/** UTC representation of (local) start date (earliest asset) */
|
||||
startDate?: string;
|
||||
/** Last update date */
|
||||
updatedAt: string;
|
||||
|
||||
@@ -131,9 +131,17 @@ export const AlbumResponseSchema = z
|
||||
.optional()
|
||||
.describe('Last modified asset timestamp'),
|
||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||
startDate: z.string().meta({ format: 'date-time' }).optional().describe('Start date (earliest asset)'),
|
||||
startDate: z
|
||||
.string()
|
||||
.meta({ format: 'date-time' })
|
||||
.optional()
|
||||
.describe('UTC representation of (local) start date (earliest asset)'),
|
||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||
endDate: z.string().meta({ format: 'date-time' }).optional().describe('End date (latest asset)'),
|
||||
endDate: z
|
||||
.string()
|
||||
.meta({ format: 'date-time' })
|
||||
.optional()
|
||||
.describe('UTC representation of (local) end date (latest asset)'),
|
||||
isActivityEnabled: z.boolean().describe('Activity feed enabled'),
|
||||
order: AssetOrderSchema.optional(),
|
||||
contributorCounts: z.array(ContributorCountResponseSchema).optional(),
|
||||
|
||||
@@ -5,7 +5,7 @@ import { JobsOptions, Queue, Worker } from 'bullmq';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { JobConfig } from 'src/decorators';
|
||||
import { QueueJobResponseDto, QueueJobSearchDto } from 'src/dtos/queue.dto';
|
||||
import { ImmichWorker, JobName, JobStatus, MetadataKey, QueueCleanType, QueueJobStatus, QueueName } from 'src/enum';
|
||||
import { JobName, JobStatus, MetadataKey, QueueCleanType, QueueJobStatus, QueueName } from 'src/enum';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
@@ -19,14 +19,10 @@ type JobMapItem = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
const WORKER_WATCH_INTERVAL_MS = 30_000;
|
||||
|
||||
@Injectable()
|
||||
export class JobRepository {
|
||||
private workers: Partial<Record<QueueName, Worker>> = {};
|
||||
private handlers: Partial<Record<JobName, JobMapItem>> = {};
|
||||
private workerWatcher?: ReturnType<typeof setInterval>;
|
||||
private microservicesPresent = true;
|
||||
|
||||
constructor(
|
||||
private moduleRef: ModuleRef,
|
||||
@@ -94,44 +90,11 @@ export class JobRepository {
|
||||
this.workers[queueName] = new Worker(
|
||||
queueName,
|
||||
(job) => this.eventRepository.emit('JobRun', queueName, job as JobItem),
|
||||
{ ...bull.config, concurrency: 1, name: ImmichWorker.Microservices },
|
||||
{ ...bull.config, concurrency: 1 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
watchWorkers() {
|
||||
this.workerWatcher ??= setInterval(() => void this.checkWorkers(), WORKER_WATCH_INTERVAL_MS);
|
||||
}
|
||||
|
||||
teardown() {
|
||||
if (this.workerWatcher) {
|
||||
clearInterval(this.workerWatcher);
|
||||
this.workerWatcher = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async checkWorkers() {
|
||||
let present: boolean;
|
||||
try {
|
||||
const suffix = `:w:${ImmichWorker.Microservices}`;
|
||||
const workers = await this.getQueue(QueueName.BackgroundTask).getWorkers();
|
||||
present = workers.some((worker) => worker.rawname?.endsWith(suffix));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.microservicesPresent !== present) {
|
||||
if (present) {
|
||||
this.logger.log('Microservices worker connected.');
|
||||
} else {
|
||||
this.logger.warn(
|
||||
'No microservices worker is connected. Background jobs will not be processed until one is running.',
|
||||
);
|
||||
}
|
||||
}
|
||||
this.microservicesPresent = present;
|
||||
}
|
||||
|
||||
async run({ name, data }: JobItem) {
|
||||
const item = this.handlers[name as JobName];
|
||||
if (!item) {
|
||||
|
||||
@@ -80,16 +80,9 @@ export class QueueService extends BaseService {
|
||||
this.jobRepository.setup(this.services);
|
||||
if (this.worker === ImmichWorker.Microservices) {
|
||||
this.jobRepository.startWorkers();
|
||||
} else if (this.worker === ImmichWorker.Api) {
|
||||
this.jobRepository.watchWorkers();
|
||||
}
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'AppShutdown' })
|
||||
onShutdown() {
|
||||
this.jobRepository.teardown();
|
||||
}
|
||||
|
||||
private updateConcurrency(config: SystemConfig) {
|
||||
this.logger.debug(`Updating queue concurrency settings`);
|
||||
for (const queueName of Object.values(QueueName)) {
|
||||
|
||||
@@ -6,8 +6,6 @@ export const newJobRepositoryMock = (): Mocked<RepositoryInterface<JobRepository
|
||||
return {
|
||||
setup: vitest.fn(),
|
||||
startWorkers: vitest.fn(),
|
||||
watchWorkers: vitest.fn(),
|
||||
teardown: vitest.fn(),
|
||||
run: vitest.fn(),
|
||||
setConcurrency: vitest.fn(),
|
||||
empty: vitest.fn(),
|
||||
|
||||
@@ -3,15 +3,18 @@
|
||||
import type { AlbumResponseDto } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
album: AlbumResponseDto;
|
||||
}
|
||||
};
|
||||
|
||||
let { album }: Props = $props();
|
||||
const { album }: Props = $props();
|
||||
const startDate = album.startDate;
|
||||
</script>
|
||||
|
||||
<span class="my-2 flex gap-2 text-sm font-medium text-gray-500" data-testid="album-details">
|
||||
<span>{getAlbumDateRange(album)}</span>
|
||||
<span>•</span>
|
||||
{#if startDate}
|
||||
<span>{getAlbumDateRange(startDate, album.endDate ?? startDate)}</span>
|
||||
<span>•</span>
|
||||
{/if}
|
||||
<span>{$t('items_count', { values: { count: album.assetCount } })}</span>
|
||||
</span>
|
||||
|
||||
@@ -34,6 +34,12 @@ export const dateFormats = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
} satisfies Intl.DateTimeFormatOptions,
|
||||
albumShort: {
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
} satisfies Intl.DateTimeFormatOptions,
|
||||
settings: {
|
||||
month: 'short',
|
||||
|
||||
@@ -1,7 +1,71 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getAlbumDateRange, getShortDateRange } from './date-time';
|
||||
|
||||
vitest.mock('$lib/stores/preferences.store', () => ({
|
||||
locale: writable('en'),
|
||||
}));
|
||||
|
||||
describe('getShortDateRange', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('TZ', 'UTC');
|
||||
locale.set('en');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.unstubAllEnvs();
|
||||
locale.set('en');
|
||||
});
|
||||
|
||||
it('should correctly return long month if start and end date are within the same month', () => {
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-01-31T00:00:00.000Z')).toEqual('January 2022');
|
||||
});
|
||||
|
||||
it('should correctly return month range if start and end date are in separate months within the same year', () => {
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-02-01T00:00:00.000Z')).toEqual('Jan – Feb 2022');
|
||||
});
|
||||
|
||||
it('should correctly return range if start and end date are in separate months and years', () => {
|
||||
expect(getShortDateRange('2021-12-01T00:00:00.000Z', '2022-01-01T00:00:00.000Z')).toEqual('Dec 2021 – Jan 2022');
|
||||
});
|
||||
|
||||
it('should correctly return long month if start and end date are within the same month, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC+6');
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-01-31T00:00:00.000Z')).toEqual('January 2022');
|
||||
});
|
||||
|
||||
it('should correctly return long month if start and end date are within the same month, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC-6');
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-01-31T00:00:00.000Z')).toEqual('January 2022');
|
||||
});
|
||||
|
||||
it('should correctly return month range if start and end date are in separate months within the same year, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC+6');
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-02-01T00:00:00.000Z')).toEqual('Jan – Feb 2022');
|
||||
});
|
||||
|
||||
it('should correctly return range if start and end date are in separate months and years, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC+6');
|
||||
expect(getShortDateRange('2021-12-01T00:00:00.000Z', '2022-01-01T00:00:00.000Z')).toEqual('Dec 2021 – Jan 2022');
|
||||
});
|
||||
|
||||
it('should correctly return range if start and end date are in separate months and years, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC-6');
|
||||
expect(getShortDateRange('2021-12-01T00:00:00.000Z', '2022-01-01T00:00:00.000Z')).toEqual('Dec 2021 – Jan 2022');
|
||||
});
|
||||
|
||||
it('should use the correct locale to return month range', () => {
|
||||
locale.set('fr');
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-02-01T00:00:00.000Z')).toEqual('janv.–févr. 2022');
|
||||
});
|
||||
|
||||
it('should use the correct locale to return month-year range', () => {
|
||||
locale.set('fr');
|
||||
expect(getShortDateRange('2021-12-01T00:00:00.000Z', '2022-01-01T00:00:00.000Z')).toEqual('déc. 2021 – janv. 2022');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAlbumDateRange', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('TZ', 'UTC');
|
||||
});
|
||||
@@ -10,57 +74,27 @@ describe('getShortDateRange', () => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('should correctly return month if start and end date are within the same month', () => {
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-01-31T00:00:00.000Z')).toEqual('Jan 2022');
|
||||
it('should work', () => {
|
||||
expect(getAlbumDateRange('2021-01-01T00:00:00Z', '2021-01-05T00:00:00Z')).toEqual('Jan 1 – 5, 2021');
|
||||
});
|
||||
|
||||
it('should correctly return month range if start and end date are in separate months within the same year', () => {
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-02-01T00:00:00.000Z')).toEqual('Jan - Feb 2022');
|
||||
it('should work with a single day range', () => {
|
||||
expect(getAlbumDateRange('2021-01-01T09:00:00Z', '2021-01-01T10:00:00Z')).toEqual('Jan 1, 2021');
|
||||
});
|
||||
|
||||
it('should correctly return range if start and end date are in separate months and years', () => {
|
||||
expect(getShortDateRange('2021-12-01T00:00:00.000Z', '2022-01-01T00:00:00.000Z')).toEqual('Dec 2021 - Jan 2022');
|
||||
});
|
||||
|
||||
it('should correctly return month if start and end date are within the same month, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC+6');
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-01-31T00:00:00.000Z')).toEqual('Jan 2022');
|
||||
});
|
||||
|
||||
it('should correctly return month range if start and end date are in separate months within the same year, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC+6');
|
||||
expect(getShortDateRange('2022-01-01T00:00:00.000Z', '2022-02-01T00:00:00.000Z')).toEqual('Jan - Feb 2022');
|
||||
it('should use the proper locale', () => {
|
||||
locale.set('fr');
|
||||
expect(getAlbumDateRange('2020-03-26T12:00:00Z', '2021-12-01T00:00:00Z')).toEqual('26 mars 2020 – 1 déc. 2021');
|
||||
locale.set('en');
|
||||
});
|
||||
|
||||
it('should correctly return range if start and end date are in separate months and years, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC+6');
|
||||
expect(getShortDateRange('2021-12-01T00:00:00.000Z', '2022-01-01T00:00:00.000Z')).toEqual('Dec 2021 - Jan 2022');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAlbumDate', () => {
|
||||
beforeAll(() => {
|
||||
process.env.TZ = 'UTC';
|
||||
|
||||
vitest.mock('$lib/stores/preferences.store', () => ({
|
||||
locale: writable('en'),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should work with only a start date', () => {
|
||||
expect(getAlbumDateRange({ startDate: '2021-01-01T00:00:00Z' })).toEqual('Jan 1, 2021');
|
||||
});
|
||||
|
||||
it('should work with a start and end date', () => {
|
||||
expect(
|
||||
getAlbumDateRange({
|
||||
startDate: '2021-01-01T00:00:00Z',
|
||||
endDate: '2021-01-05T00:00:00Z',
|
||||
}),
|
||||
).toEqual('Jan 1, 2021 - Jan 5, 2021');
|
||||
});
|
||||
|
||||
it('should work with the new date format', () => {
|
||||
expect(getAlbumDateRange({ startDate: '2021-01-01T00:00:00+05:00' })).toEqual('Jan 1, 2021');
|
||||
expect(getAlbumDateRange('2021-12-01T00:00:00Z', '2022-01-01T00:00:00Z')).toEqual('Dec 1, 2021 – Jan 1, 2022');
|
||||
});
|
||||
|
||||
it('should correctly return range if start and end date are in separate months and years, ignoring local time zone', () => {
|
||||
vi.stubEnv('TZ', 'UTC-6');
|
||||
expect(getAlbumDateRange('2021-12-01T00:00:00Z', '2022-01-01T00:00:00Z')).toEqual('Dec 1, 2021 – Jan 1, 2022');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,69 +7,34 @@ export function parseUtcDate(date: string) {
|
||||
return DateTime.fromISO(date, { zone: 'UTC' }).toUTC();
|
||||
}
|
||||
|
||||
export const getShortDateRange = (startTimestamp: string, endTimestamp: string) => {
|
||||
const getDateRange = (startTimestamp: string, endTimestamp: string, format: 'short' | 'long') => {
|
||||
// We don't need to check if the locale is set/nonempty. MDN's Intl docs:
|
||||
// "If the application doesn't provide a locales argument, or the runtime doesn't have a locale that matches the request, then the runtime's default locale is used."
|
||||
const userLocale = get(locale);
|
||||
let startDate = DateTime.fromISO(startTimestamp).setZone('UTC');
|
||||
let endDate = DateTime.fromISO(endTimestamp).setZone('UTC');
|
||||
const startDate = DateTime.fromISO(startTimestamp).setZone('UTC');
|
||||
const endDate = DateTime.fromISO(endTimestamp).setZone('UTC');
|
||||
|
||||
if (userLocale) {
|
||||
startDate = startDate.setLocale(userLocale);
|
||||
endDate = endDate.setLocale(userLocale);
|
||||
if (startDate.year === endDate.year && startDate.month === endDate.month && format === 'short') {
|
||||
return endDate.setLocale(userLocale).toLocaleString({ month: 'long', year: 'numeric' });
|
||||
}
|
||||
|
||||
const endDateLocalized = endDate.toLocaleString({
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
});
|
||||
|
||||
if (startDate.year === endDate.year) {
|
||||
if (startDate.month === endDate.month) {
|
||||
// Same year and month.
|
||||
// e.g.: aug. 2024
|
||||
return endDateLocalized;
|
||||
} else {
|
||||
// Same year but different month.
|
||||
// e.g.: jul. - sept. 2024
|
||||
const startMonthLocalized = startDate.toLocaleString({
|
||||
month: 'short',
|
||||
});
|
||||
return `${startMonthLocalized} - ${endDateLocalized}`;
|
||||
}
|
||||
} else {
|
||||
// Different year.
|
||||
// e.g.: feb. 2021 - sept. 2024
|
||||
const startDateLocalized = startDate.toLocaleString({
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
});
|
||||
return `${startDateLocalized} - ${endDateLocalized}`;
|
||||
}
|
||||
const formatter = new Intl.DateTimeFormat(
|
||||
userLocale,
|
||||
format === 'short' ? dateFormats.albumShort : dateFormats.album,
|
||||
);
|
||||
return formatter.formatRange(startDate.toJSDate(), endDate.toJSDate());
|
||||
};
|
||||
|
||||
const formatDate = (date?: string) => {
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Get localized date range in short format like 'Oct – Nov 2026', with full month if start and end are the same: 'October 2026'.
|
||||
* Timestamps are expected to be date-only in UTC.
|
||||
*/
|
||||
export const getShortDateRange = (start: string, end: string) => getDateRange(start, end, 'short');
|
||||
|
||||
// without timezone
|
||||
const localDate = date.replace(/Z$/, '').replace(/\+.+$/, '');
|
||||
return localDate ? new Date(localDate).toLocaleDateString(get(locale), dateFormats.album) : undefined;
|
||||
};
|
||||
|
||||
export const getAlbumDateRange = (album: { startDate?: string; endDate?: string }) => {
|
||||
const start = formatDate(album.startDate);
|
||||
const end = formatDate(album.endDate);
|
||||
|
||||
if (start && end && start !== end) {
|
||||
return `${start} - ${end}`;
|
||||
}
|
||||
|
||||
if (start) {
|
||||
return start;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
/**
|
||||
* Get localized date range in long format. Timestamps are expected to be date-only in UTC.
|
||||
*/
|
||||
export const getAlbumDateRange = (start: string, end: string) => getDateRange(start, end, 'long');
|
||||
|
||||
/**
|
||||
* Use this to convert from "5pm EST" to "5pm UTC"
|
||||
|
||||
Reference in New Issue
Block a user