Compare commits

..

5 Commits

Author SHA1 Message Date
midzelis
f07206667a fix(web): preserve face bounding boxes during face editing
Change-Id: I2370565cd8f706ab4d130e83241ddf086a6a6964
2026-04-25 16:03:16 +00:00
Savely Krasovsky
9263e2f2e1 feat(ml): update Intel graphics compiler and compute runtime (#28076)
feat(ml): update Intel graphics compiler and compute runtime to latest versions
2026-04-25 08:49:57 -04:00
Aaron Liu
a3ee615c5b chore(ml): update huggingfacehub and pillow (#27552) 2026-04-24 19:44:01 -04:00
Yaros
39cfad7136 feat(mobile): action bottom sheet on map timeline (#27515) 2026-04-24 09:30:10 -05:00
renovate[bot]
350056dd1a fix(deps): update dependency uuid to v14 [security] (#28046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 11:24:33 +02:00
14 changed files with 71 additions and 102 deletions

View File

@@ -48,14 +48,14 @@ FROM python:3.13-slim-trixie@sha256:d168b8d9eb761f4d3fe305ebd04aeb7e7f2de0297cec
RUN apt-get update && \
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-core-2_2.28.4+20760_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-opencl-2_2.28.4+20760_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/intel-opencl-icd_26.05.37020.3-0_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-core-2_2.32.7+21184_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.32.7/intel-igc-opencl-2_2.32.7+21184_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/intel-opencl-icd_26.14.37833.4-0_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/libigdgmm12_22.9.0_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/26.14.37833.4/libigdgmm12_22.9.0_amd64.deb && \
dpkg -i *.deb && \
rm *.deb && \
apt-get remove wget -yqq && \

View File

@@ -9,12 +9,12 @@ dependencies = [
"aiocache>=0.12.1,<1.0",
"fastapi>=0.95.2,<1.0",
"gunicorn>=21.1.0",
"huggingface-hub>=0.20.1,<1.0",
"huggingface-hub>=1.0,<2.0",
"insightface>=0.7.3,<1.0",
"numpy<2.4.0",
"opencv-python-headless>=4.7.0.72,<5.0",
"orjson>=3.9.5",
"pillow>=12.2,<12.3",
"pillow>=12.2,<13",
"pydantic>=2.0.0,<3",
"pydantic-settings>=2.5.2,<3",
"python-multipart>=0.0.6,<1.0",

View File

@@ -17,7 +17,6 @@
A01DD69B2F7F43B40049AB63 /* ImageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A01DD6982F7F43B40049AB63 /* ImageRequest.swift */; };
B21E34AA2E5AFD2B0031FDB9 /* BackgroundWorkerApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34A92E5AFD210031FDB9 /* BackgroundWorkerApiImpl.swift */; };
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */; };
B21E34B02E5B09190031FDB9 /* FileLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34B12E5B09100031FDB9 /* FileLogger.swift */; };
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */; };
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */; };
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; };
@@ -104,7 +103,6 @@
B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = "<group>"; };
B21E34A92E5AFD210031FDB9 /* BackgroundWorkerApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorkerApiImpl.swift; sourceTree = "<group>"; };
B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.swift; sourceTree = "<group>"; };
B21E34B12E5B09100031FDB9 /* FileLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogger.swift; sourceTree = "<group>"; };
B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connectivity.g.swift; sourceTree = "<group>"; };
B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityApiImpl.swift; sourceTree = "<group>"; };
B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = "<group>"; };
@@ -306,7 +304,6 @@
B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */,
B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */,
B21E34A92E5AFD210031FDB9 /* BackgroundWorkerApiImpl.swift */,
B21E34B12E5B09100031FDB9 /* FileLogger.swift */,
);
path = Background;
sourceTree = "<group>";
@@ -617,7 +614,6 @@
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
A01DD69B2F7F43B40049AB63 /* ImageRequest.swift in Sources */,
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */,
B21E34B02E5B09190031FDB9 /* FileLogger.swift in Sources */,
FE5499F32F1197D8006016CB /* LocalImages.g.swift in Sources */,
FE5499F62F11980E006016CB /* LocalImagesImpl.swift in Sources */,
FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */,

View File

@@ -80,34 +80,29 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
* starts the engine, and sets up a timeout timer if specified.
*/
func run() {
FileLogger.log("BackgroundWorker:run Starting Flutter engine for taskType=\(taskType) maxSeconds=\(maxSeconds.map(String.init) ?? "nil")")
// Start the Flutter engine with the specified callback as the entry point
let isRunning = engine.run(
withEntrypoint: "backgroundSyncNativeEntrypoint",
libraryURI: "package:immich_mobile/domain/services/background_worker.service.dart"
)
// Verify that the Flutter engine started successfully
if !isRunning {
FileLogger.log("BackgroundWorker:run Flutter engine failed to start, completing with success=false")
complete(success: false)
return
}
FileLogger.log("BackgroundWorker:run Flutter engine started")
// Register plugins in the new engine
GeneratedPluginRegistrant.register(with: engine)
// Register custom plugins
AppDelegate.registerPlugins(with: engine, messenger: engine.binaryMessenger)
flutterApi = BackgroundWorkerFlutterApi(binaryMessenger: engine.binaryMessenger)
BackgroundWorkerBgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: self)
FileLogger.log("BackgroundWorker:run Plugins registered, waiting for Flutter onInitialized")
// Set up a timeout timer if maxSeconds was specified to prevent runaway background tasks
if maxSeconds != nil {
// Schedule a timer to cancel the task after the specified timeout period
Timer.scheduledTimer(withTimeInterval: TimeInterval(maxSeconds!), repeats: false) { _ in
FileLogger.log("BackgroundWorker:run maxSeconds=\(self.maxSeconds!) timer fired, closing task")
self.close()
}
}
@@ -119,7 +114,6 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
* This method acts as a bridge between the native iOS background task system and Flutter.
*/
func onInitialized() throws {
FileLogger.log("BackgroundWorker:onInitialized Flutter ready, calling onIosUpload isRefresh=\(self.taskType == .refresh)")
flutterApi?.onIosUpload(isRefresh: self.taskType == .refresh, maxSeconds: maxSeconds.map { Int64($0) }, completion: { result in
self.handleHostResult(result: result)
})
@@ -132,22 +126,16 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
*/
func close() {
if isComplete {
FileLogger.log("BackgroundWorker:close Already complete, ignoring close()")
return
}
FileLogger.log("BackgroundWorker:close Cancel requested, signaling Flutter (taskType=\(taskType))")
flutterApi?.cancel { result in
FileLogger.log("BackgroundWorker:close Flutter cancel acknowledged")
self.complete(success: false)
}
// Fallback safety mechanism: ensure completion is called within 2 seconds
// This prevents the background task from hanging indefinitely if Flutter doesn't respond
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
if !self.isComplete {
FileLogger.log("BackgroundWorker:close 2s fallback fired, Flutter did not acknowledge cancel")
}
self.complete(success: false)
}
}
@@ -161,12 +149,8 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
*/
private func handleHostResult(result: Result<Void, PigeonError>) {
switch result {
case .success():
FileLogger.log("BackgroundWorker:handleHostResult Flutter onIosUpload succeeded (taskType=\(taskType))")
self.complete(success: true)
case .failure(let error):
FileLogger.log("BackgroundWorker:handleHostResult Flutter onIosUpload failed: \(error.localizedDescription) (taskType=\(taskType))")
self.close()
case .success(): self.complete(success: true)
case .failure(_): self.close()
}
}
@@ -182,8 +166,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
if(isComplete) {
return
}
FileLogger.log("BackgroundWorker:complete Tearing down engine, success=\(success) (taskType=\(taskType))")
isComplete = true
AppDelegate.cancelPlugins(with: engine)
engine.destroyContext()

View File

@@ -5,7 +5,7 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
func enable() throws {
BackgroundWorkerApiImpl.scheduleRefreshWorker()
BackgroundWorkerApiImpl.scheduleProcessingWorker()
FileLogger.log("BackgroundWorkerApiImpl:enable Background worker scheduled")
print("BackgroundWorkerApiImpl:enable Background worker scheduled")
}
func configure(settings: BackgroundWorkerSettings) throws {
@@ -19,7 +19,7 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
func disable() throws {
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID);
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID);
FileLogger.log("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers")
print("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers")
}
private static let refreshTaskID = "app.alextran.immich.background.refreshUpload"
@@ -30,7 +30,6 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: processingTaskID, using: nil) { task in
if task is BGProcessingTask {
FileLogger.log("BackgroundWorkerApiImpl:BGProcessingTask Background Processing task received")
handleBackgroundProcessing(task: task as! BGProcessingTask)
}
}
@@ -38,11 +37,9 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: refreshTaskID, using: nil) { task in
if task is BGAppRefreshTask {
FileLogger.log("BackgroundWorkerApiImpl:BGAppRefreshTask Background Refresh task received")
handleBackgroundRefresh(task: task as! BGAppRefreshTask)
}
}
FileLogger.log("BackgroundWorkerApiImpl:registerBackgroundWorkers Background workers registered")
}
private static func scheduleRefreshWorker() {
@@ -51,9 +48,8 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
do {
try BGTaskScheduler.shared.submit(backgroundRefresh)
FileLogger.log("BackgroundWorkerApiImpl:scheduleRefreshWorker Scheduled Refresh task")
} catch {
FileLogger.log("BackgroundWorkerApiImpl:scheduleRefreshWorker Could not schedule the refresh upload task \(error.localizedDescription)")
print("Could not schedule the refresh upload task \(error.localizedDescription)")
}
}
@@ -65,32 +61,25 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
do {
try BGTaskScheduler.shared.submit(backgroundProcessing)
FileLogger.log("BackgroundWorkerApiImpl:scheduleProcessingWorker Scheduled Processing task")
} catch {
FileLogger.log("BackgroundWorkerApiImpl:scheduleProcessingWorker Could not schedule the processing upload task \(error.localizedDescription)")
print("Could not schedule the processing upload task \(error.localizedDescription)")
}
}
private static func handleBackgroundRefresh(task: BGAppRefreshTask) {
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundRefresh Entered, re-queuing next refresh task")
scheduleRefreshWorker()
// If another task is running, cede the background time back to the OS
if taskSemaphore.wait(timeout: .now()) == .success {
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundRefresh Starting background worker")
// Restrict the refresh task to run only for a maximum of (maxSeconds) seconds
runBackgroundWorker(task: task, taskType: .refresh, maxSeconds: 20)
} else {
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundRefresh Processing task is in progress")
task.setTaskCompleted(success: true)
task.setTaskCompleted(success: false)
}
}
private static func handleBackgroundProcessing(task: BGProcessingTask) {
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundProcessing Entered, re-queuing next processing task")
scheduleProcessingWorker()
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundProcessing Waiting for taskSemaphore")
taskSemaphore.wait()
FileLogger.log("BackgroundWorkerApiImpl:handleBackgroundProcessing Semaphore acquired, starting background worker")
// There are no restrictions for processing tasks. Although, the OS could signal expiration at any time
runBackgroundWorker(task: task, taskType: .processing, maxSeconds: nil)
}
@@ -116,12 +105,11 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
}
task.expirationHandler = {
FileLogger.log("BackgroundWorkerApiImpl:runBackgroundWorker iOS signaled expiration (taskType=\(taskType)), closing worker")
DispatchQueue.main.async {
backgroundWorker.close()
}
isSuccess = false
// Schedule a timer to signal the semaphore after 2 seconds
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
semaphore.signal()
@@ -134,6 +122,6 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
semaphore.wait()
task.setTaskCompleted(success: isSuccess)
FileLogger.log("BackgroundWorkerApiImpl:runBackgroundWorker Background task completed with success: \(isSuccess)")
print("Background task completed with success: \(isSuccess)")
}
}

View File

@@ -1,33 +0,0 @@
import Foundation
enum FileLogger {
private static let queue = DispatchQueue(label: "app.alextran.immich.FileLogger")
private static let isoFormatter: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return f
}()
private static var logFileURL: URL? {
guard let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
else { return nil }
return docs.appendingPathComponent("background_log.txt")
}
static func log(_ message: String) {
let line = "[\(isoFormatter.string(from: Date()))] \(message)\n"
print(line, terminator: "")
queue.async {
guard let url = logFileURL, let data = line.data(using: .utf8) else { return }
if FileManager.default.fileExists(atPath: url.path) {
if let handle = try? FileHandle(forWritingTo: url) {
defer { try? handle.close() }
try? handle.seekToEnd()
try? handle.write(contentsOf: data)
}
} else {
try? data.write(to: url, options: .atomic)
}
}
}
}

View File

@@ -115,9 +115,7 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<string>No</string>
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true/>
<key>NSAppTransportSecurity</key>

View File

@@ -2,17 +2,21 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
class MapBottomSheet extends StatelessWidget {
const MapBottomSheet({super.key});
final Key? sheetKey;
const MapBottomSheet({super.key, this.sheetKey});
@override
Widget build(BuildContext context) {
return BaseBottomSheet(
key: sheetKey,
initialChildSize: 0.25,
maxChildSize: 0.75,
shouldCloseOnMinExtent: false,
@@ -49,7 +53,7 @@ class _ScopedMapTimeline extends StatelessWidget {
return timelineService;
}),
],
child: const Timeline(appBar: null, bottomSheet: null, withScrubber: false),
child: const Timeline(appBar: null, bottomSheet: GeneralBottomSheet(minChildSize: 0.23), withScrubber: false),
);
}
}

View File

@@ -11,6 +11,7 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
@@ -53,6 +54,7 @@ class _DriftMapState extends ConsumerState<DriftMap> {
final _reloadMutex = AsyncMutex();
final _debouncer = Debouncer(interval: const Duration(milliseconds: 500), maxWaitTime: const Duration(seconds: 2));
final ValueNotifier<double> bottomSheetOffset = ValueNotifier(0.25);
final GlobalKey _bottomSheetKey = GlobalKey();
StreamSubscription? _eventSubscription;
@override
@@ -184,7 +186,7 @@ class _DriftMapState extends ConsumerState<DriftMap> {
return Stack(
children: [
_Map(initialLocation: widget.initialLocation, onMapCreated: onMapCreated, onMapReady: onMapReady),
_DynamicBottomSheet(bottomSheetOffset: bottomSheetOffset),
_DynamicBottomSheet(bottomSheetOffset: bottomSheetOffset, sheetKey: _bottomSheetKey),
_DynamicMyLocationButton(onZoomToLocation: onZoomToLocation, bottomSheetOffset: bottomSheetOffset),
],
);
@@ -224,8 +226,9 @@ class _Map extends StatelessWidget {
class _DynamicBottomSheet extends StatefulWidget {
final ValueNotifier<double> bottomSheetOffset;
final GlobalKey sheetKey;
const _DynamicBottomSheet({required this.bottomSheetOffset});
const _DynamicBottomSheet({required this.bottomSheetOffset, required this.sheetKey});
@override
State<_DynamicBottomSheet> createState() => _DynamicBottomSheetState();
@@ -236,10 +239,13 @@ class _DynamicBottomSheetState extends State<_DynamicBottomSheet> {
Widget build(BuildContext context) {
return NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
widget.bottomSheetOffset.value = notification.extent;
return true;
final sheet = notification.context.findAncestorWidgetOfExactType<BaseBottomSheet>();
if (sheet?.key == widget.sheetKey) {
widget.bottomSheetOffset.value = notification.extent;
}
return false;
},
child: const MapBottomSheet(),
child: MapBottomSheet(sheetKey: widget.sheetKey),
);
}
}

View File

@@ -469,6 +469,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
ref.read(timelineStateProvider.notifier).setScrolling(true);
},
child: Stack(
clipBehavior: Clip.none,
children: [
timeline,
if (isBottomWidgetVisible)

10
pnpm-lock.yaml generated
View File

@@ -570,8 +570,8 @@ importers:
specifier: ^2.0.0
version: 2.0.9
uuid:
specifier: ^11.1.0
version: 11.1.0
specifier: ^14.0.0
version: 14.0.0
validator:
specifier: ^13.12.0
version: 13.15.35
@@ -12110,6 +12110,10 @@ packages:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true
uuid@14.0.0:
resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==}
hasBin: true
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -25779,6 +25783,8 @@ snapshots:
uuid@11.1.0: {}
uuid@14.0.0: {}
uuid@8.3.2: {}
validator@13.15.35: {}

View File

@@ -114,7 +114,7 @@
"thumbhash": "^0.1.1",
"transformation-matrix": "^3.1.0",
"ua-parser-js": "^2.0.0",
"uuid": "^11.1.0",
"uuid": "^14.0.0",
"validator": "^13.12.0",
"zod": "^4.3.6"
},

View File

@@ -170,11 +170,18 @@
const faces = $derived(Array.from(faceToNameMap.keys()));
const handleImageMouseMove = (event: MouseEvent) => {
$boundingBoxesArray = [];
if (!assetViewerManager.imgRef || !element || assetViewerManager.isFaceEditMode || ocrManager.showOverlay) {
if (
!assetViewerManager.imgRef ||
!element ||
assetViewerManager.isFaceEditMode ||
assetViewerManager.isEditFacesPanelOpen ||
ocrManager.showOverlay
) {
return;
}
$boundingBoxesArray = [];
const natural = getNaturalSize(assetViewerManager.imgRef);
const scaled = scaleToFit(natural, container);
const { currentZoom, currentPositionX, currentPositionY } = assetViewerManager.zoomState;
@@ -196,6 +203,9 @@
};
const handleImageMouseLeave = () => {
if (assetViewerManager.isEditFacesPanelOpen) {
return;
}
$boundingBoxesArray = [];
};
</script>

View File

@@ -146,6 +146,7 @@
selectedPersonToCreate[editedFace.id] = newFeaturePhoto;
}
showSelectedFaces = false;
$boundingBoxesArray = [];
};
const handleReassignFace = (person: PersonResponseDto | null) => {
@@ -153,11 +154,13 @@
selectedPersonToReassign[editedFace.id] = person;
}
showSelectedFaces = false;
$boundingBoxesArray = [];
};
const handleFacePicker = (face: AssetFaceResponseDto) => {
editedFace = face;
showSelectedFaces = true;
$boundingBoxesArray = [face];
};
const deleteAssetFace = async (face: AssetFaceResponseDto) => {
@@ -247,7 +250,11 @@
class="absolute start-0 top-0 h-22.5 w-22.5 cursor-default"
onfocus={() => ($boundingBoxesArray = [peopleWithFaces[index]])}
onmouseover={() => ($boundingBoxesArray = [peopleWithFaces[index]])}
onmouseleave={() => ($boundingBoxesArray = [])}
onmouseleave={() => {
if (!showSelectedFaces) {
$boundingBoxesArray = [];
}
}}
>
<div class="relative">
{#if selectedPersonToCreate[face.id]}
@@ -384,7 +391,10 @@
{editedFace}
{assetId}
{assetType}
onClose={() => (showSelectedFaces = false)}
onClose={() => {
showSelectedFaces = false;
$boundingBoxesArray = [];
}}
onCreatePerson={handleCreatePerson}
onReassign={handleReassignFace}
/>