From 9184210f824ca7877c0e4da821947d31c8c68f51 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 13 Jul 2026 00:33:46 +0200 Subject: [PATCH 1/6] fix depth slider on android --- src/components/panel/right/MasksPanel.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/panel/right/MasksPanel.tsx b/src/components/panel/right/MasksPanel.tsx index 6cc94c1fbe..52f4d9f472 100644 --- a/src/components/panel/right/MasksPanel.tsx +++ b/src/components/panel/right/MasksPanel.tsx @@ -429,7 +429,7 @@ function DepthRangePicker({ {t('editor.masks.depthRange.reset')} -
+
{isDragging && (
@@ -523,7 +524,7 @@ function DepthRangePicker({
@@ -539,7 +540,7 @@ function DepthRangePicker({
From 52eddc9dcb7f85145dbd43a6496666d0a53db062 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 13 Jul 2026 00:40:54 +0200 Subject: [PATCH 2/6] add android touch support to Denoise and Negative conversion --- src/components/modals/DenoiseModal.tsx | 131 +++++++++++++++++- .../modals/NegativeConversionModal.tsx | 91 +++++++++++- 2 files changed, 220 insertions(+), 2 deletions(-) diff --git a/src/components/modals/DenoiseModal.tsx b/src/components/modals/DenoiseModal.tsx index 1dfc24e0af..3eab44c7b7 100644 --- a/src/components/modals/DenoiseModal.tsx +++ b/src/components/modals/DenoiseModal.tsx @@ -38,6 +38,20 @@ const ImageCompare = ({ original, denoised }: { original: string; denoised: stri const containerRef = useRef(null); const lastMousePos = useRef({ x: 0, y: 0 }); + const [isTouchDragging, setIsTouchDragging] = useState(false); + const touchState = useRef({ + initialPinchDist: 0, + initialZoom: 1, + initialPanX: 0, + initialPanY: 0, + lastCenterX: 0, + lastCenterY: 0, + lastTouchX: 0, + lastTouchY: 0, + isPinching: false, + isPanning: false, + isSliding: false, + }); useEffect(() => { if (!isDragging && !isResizingSlider) return; @@ -107,9 +121,119 @@ const ImageCompare = ({ original, denoised }: { original: string; denoised: stri setPan({ x: newPanX, y: newPanY }); }; + const handleTouchStart = (e: React.TouchEvent) => { + if (e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + touchState.current = { + initialPinchDist: Math.hypot(dx, dy), + initialZoom: zoom, + initialPanX: pan.x, + initialPanY: pan.y, + lastCenterX: (e.touches[0].clientX + e.touches[1].clientX) / 2, + lastCenterY: (e.touches[0].clientY + e.touches[1].clientY) / 2, + lastTouchX: 0, + lastTouchY: 0, + isPinching: true, + isPanning: false, + isSliding: false, + }; + } else if (e.touches.length === 1) { + const target = e.target as HTMLElement; + const isSlider = target.closest('[data-slider]'); + if (isSlider) { + e.preventDefault(); + touchState.current = { + ...touchState.current, + isPinching: false, + isPanning: false, + isSliding: true, + }; + setIsResizingSlider(true); + const rect = containerRef.current?.getBoundingClientRect(); + if (rect) { + const x = Math.max(0, Math.min(e.touches[0].clientX - rect.left, rect.width)); + setSliderPosition((x / rect.width) * 100); + } + } else { + e.preventDefault(); + touchState.current = { + ...touchState.current, + lastTouchX: e.touches[0].clientX, + lastTouchY: e.touches[0].clientY, + isPinching: false, + isPanning: true, + isSliding: false, + }; + setIsTouchDragging(true); + } + } + }; + + const handleTouchMove = (e: React.TouchEvent) => { + if (touchState.current.isPinching && e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + const dist = Math.hypot(dx, dy); + const scale = dist / touchState.current.initialPinchDist; + const newZoom = Math.min(Math.max(0.5, touchState.current.initialZoom * scale), 4); + + const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2; + const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2; + const cx = centerX - touchState.current.lastCenterX; + const cy = centerY - touchState.current.lastCenterY; + + setZoom(newZoom); + setPan((prev) => ({ x: prev.x + cx, y: prev.y + cy })); + touchState.current.lastCenterX = centerX; + touchState.current.lastCenterY = centerY; + } else if (touchState.current.isPanning && e.touches.length === 1) { + e.preventDefault(); + const dx = e.touches[0].clientX - touchState.current.lastTouchX; + const dy = e.touches[0].clientY - touchState.current.lastTouchY; + setPan((prev) => ({ x: prev.x + dx, y: prev.y + dy })); + touchState.current.lastTouchX = e.touches[0].clientX; + touchState.current.lastTouchY = e.touches[0].clientY; + } else if (touchState.current.isSliding && e.touches.length === 1) { + e.preventDefault(); + const rect = containerRef.current?.getBoundingClientRect(); + if (rect) { + const x = Math.max(0, Math.min(e.touches[0].clientX - rect.left, rect.width)); + setSliderPosition((x / rect.width) * 100); + } + } + }; + + const handleTouchEnd = (e: React.TouchEvent) => { + if (e.touches.length === 0) { + touchState.current.isPinching = false; + touchState.current.isPanning = false; + touchState.current.isSliding = false; + setIsTouchDragging(false); + setIsResizingSlider(false); + } else if (e.touches.length === 1) { + touchState.current.isPinching = false; + const target = e.target as HTMLElement; + const isSlider = target.closest('[data-slider]'); + if (isSlider) { + touchState.current.isSliding = true; + touchState.current.isPanning = false; + setIsResizingSlider(true); + } else { + touchState.current.isPanning = true; + touchState.current.isSliding = false; + touchState.current.lastTouchX = e.touches[0].clientX; + touchState.current.lastTouchY = e.touches[0].clientY; + setIsTouchDragging(true); + } + } + }; + const imageTransformStyle = { transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`, - transition: isDragging || isResizingSlider ? 'none' : 'transform 0.1s ease-out', + transition: isDragging || isResizingSlider || isTouchDragging ? 'none' : 'transform 0.1s ease-out', transformOrigin: 'center center', }; @@ -143,8 +267,12 @@ const ImageCompare = ({ original, denoised }: { original: string; denoised: stri
@@ -177,6 +305,7 @@ const ImageCompare = ({ original, denoised }: { original: string; denoised: stri className="absolute top-0 bottom-0 w-0.5 bg-white cursor-col-resize z-10 shadow-[0_0_8px_rgba(0,0,0,0.8)]" style={{ left: `${sliderPosition}%` }} onMouseDown={handleSliderMouseDown} + data-slider >
diff --git a/src/components/modals/NegativeConversionModal.tsx b/src/components/modals/NegativeConversionModal.tsx index 23b85990e6..4ddd4d1b90 100644 --- a/src/components/modals/NegativeConversionModal.tsx +++ b/src/components/modals/NegativeConversionModal.tsx @@ -56,6 +56,19 @@ export default function NegativeConversionModal({ const containerRef = useRef(null); const lastMousePos = useRef({ x: 0, y: 0 }); + const [isTouchDragging, setIsTouchDragging] = useState(false); + const touchState = useRef({ + initialPinchDist: 0, + initialZoom: 1, + initialPanX: 0, + initialPanY: 0, + lastCenterX: 0, + lastCenterY: 0, + lastTouchX: 0, + lastTouchY: 0, + isPinching: false, + isDragging: false, + }); const [originalUrl, setOriginalUrl] = useState(null); const selectedImagePath = targetPaths.length > 0 ? targetPaths[0] : null; @@ -191,9 +204,81 @@ export default function NegativeConversionModal({ } }; + const handleTouchStart = (e: React.TouchEvent) => { + if (e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + touchState.current = { + initialPinchDist: Math.hypot(dx, dy), + initialZoom: zoom, + initialPanX: pan.x, + initialPanY: pan.y, + lastCenterX: (e.touches[0].clientX + e.touches[1].clientX) / 2, + lastCenterY: (e.touches[0].clientY + e.touches[1].clientY) / 2, + lastTouchX: 0, + lastTouchY: 0, + isPinching: true, + isDragging: false, + }; + } else if (e.touches.length === 1) { + e.preventDefault(); + touchState.current = { + ...touchState.current, + lastTouchX: e.touches[0].clientX, + lastTouchY: e.touches[0].clientY, + isPinching: false, + isDragging: true, + }; + setIsTouchDragging(true); + } + }; + + const handleTouchMove = (e: React.TouchEvent) => { + if (touchState.current.isPinching && e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + const dist = Math.hypot(dx, dy); + const scale = dist / touchState.current.initialPinchDist; + const newZoom = Math.min(Math.max(0.1, touchState.current.initialZoom * scale), 8); + + const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2; + const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2; + const cx = centerX - touchState.current.lastCenterX; + const cy = centerY - touchState.current.lastCenterY; + + setZoom(newZoom); + setPan((prev) => ({ x: prev.x + cx, y: prev.y + cy })); + touchState.current.lastCenterX = centerX; + touchState.current.lastCenterY = centerY; + } else if (touchState.current.isDragging && e.touches.length === 1) { + e.preventDefault(); + const dx = e.touches[0].clientX - touchState.current.lastTouchX; + const dy = e.touches[0].clientY - touchState.current.lastTouchY; + setPan((prev) => ({ x: prev.x + dx, y: prev.y + dy })); + touchState.current.lastTouchX = e.touches[0].clientX; + touchState.current.lastTouchY = e.touches[0].clientY; + } + }; + + const handleTouchEnd = (e: React.TouchEvent) => { + if (e.touches.length === 0) { + touchState.current.isPinching = false; + touchState.current.isDragging = false; + setIsTouchDragging(false); + } else if (e.touches.length === 1) { + touchState.current.isPinching = false; + touchState.current.isDragging = true; + touchState.current.lastTouchX = e.touches[0].clientX; + touchState.current.lastTouchY = e.touches[0].clientY; + setIsTouchDragging(true); + } + }; + const imageTransformStyle = { transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`, - transition: isDragging ? 'none' : 'transform 0.1s ease-out', + transition: isDragging || isTouchDragging ? 'none' : 'transform 0.1s ease-out', transformOrigin: 'center center', }; @@ -326,8 +411,12 @@ export default function NegativeConversionModal({
Date: Mon, 13 Jul 2026 01:00:21 +0200 Subject: [PATCH 3/6] add pinch and zoom to lens correction view --- src/components/modals/LensCorrectionModal.tsx | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/src/components/modals/LensCorrectionModal.tsx b/src/components/modals/LensCorrectionModal.tsx index 3fb3695f08..ff0a526244 100644 --- a/src/components/modals/LensCorrectionModal.tsx +++ b/src/components/modals/LensCorrectionModal.tsx @@ -147,6 +147,19 @@ export default function LensCorrectionModal({ const [isDragging, setIsDragging] = useState(false); const containerRef = useRef(null); const lastMousePos = useRef({ x: 0, y: 0 }); + const [isTouchDragging, setIsTouchDragging] = useState(false); + const touchState = useRef({ + initialPinchDist: 0, + initialZoom: 1, + initialPanX: 0, + initialPanY: 0, + lastCenterX: 0, + lastCenterY: 0, + lastTouchX: 0, + lastTouchY: 0, + isPinching: false, + isDragging: false, + }); const [modeBubbleStyle, setModeBubbleStyle] = useState({}); const isModeInitialAnimation = useRef(true); @@ -227,6 +240,78 @@ export default function LensCorrectionModal({ setPan({ x: newPanX, y: newPanY }); }; + const handleTouchStart = (e: React.TouchEvent) => { + if (e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + touchState.current = { + initialPinchDist: Math.hypot(dx, dy), + initialZoom: zoom, + initialPanX: pan.x, + initialPanY: pan.y, + lastCenterX: (e.touches[0].clientX + e.touches[1].clientX) / 2, + lastCenterY: (e.touches[0].clientY + e.touches[1].clientY) / 2, + lastTouchX: 0, + lastTouchY: 0, + isPinching: true, + isDragging: false, + }; + } else if (e.touches.length === 1) { + e.preventDefault(); + touchState.current = { + ...touchState.current, + lastTouchX: e.touches[0].clientX, + lastTouchY: e.touches[0].clientY, + isPinching: false, + isDragging: true, + }; + setIsTouchDragging(true); + } + }; + + const handleTouchMove = (e: React.TouchEvent) => { + if (touchState.current.isPinching && e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + const dist = Math.hypot(dx, dy); + const scale = dist / touchState.current.initialPinchDist; + const newZoom = Math.min(Math.max(0.1, touchState.current.initialZoom * scale), 8); + + const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2; + const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2; + const cx = centerX - touchState.current.lastCenterX; + const cy = centerY - touchState.current.lastCenterY; + + setZoom(newZoom); + setPan((prev) => ({ x: prev.x + cx, y: prev.y + cy })); + touchState.current.lastCenterX = centerX; + touchState.current.lastCenterY = centerY; + } else if (touchState.current.isDragging && e.touches.length === 1) { + e.preventDefault(); + const dx = e.touches[0].clientX - touchState.current.lastTouchX; + const dy = e.touches[0].clientY - touchState.current.lastTouchY; + setPan((prev) => ({ x: prev.x + dx, y: prev.y + dy })); + touchState.current.lastTouchX = e.touches[0].clientX; + touchState.current.lastTouchY = e.touches[0].clientY; + } + }; + + const handleTouchEnd = (e: React.TouchEvent) => { + if (e.touches.length === 0) { + touchState.current.isPinching = false; + touchState.current.isDragging = false; + setIsTouchDragging(false); + } else if (e.touches.length === 1) { + touchState.current.isPinching = false; + touchState.current.isDragging = true; + touchState.current.lastTouchX = e.touches[0].clientX; + touchState.current.lastTouchY = e.touches[0].clientY; + setIsTouchDragging(true); + } + }; + const handleResetZoom = (e?: React.MouseEvent) => { e?.stopPropagation(); setZoom(1); @@ -533,7 +618,7 @@ export default function LensCorrectionModal({ const imageTransformStyle = { transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`, - transition: isDragging ? 'none' : 'transform 0.1s ease-out', + transition: isDragging || isTouchDragging ? 'none' : 'transform 0.1s ease-out', transformOrigin: 'center center', }; @@ -875,8 +960,12 @@ export default function LensCorrectionModal({
Date: Mon, 13 Jul 2026 01:10:25 +0200 Subject: [PATCH 4/6] add initial multi-selection capability for android --- src/components/panel/MainLibrary.tsx | 220 +++++++++++------- src/components/panel/library/LibraryGrid.tsx | 6 +- src/components/panel/library/LibraryItems.tsx | 55 ++++- 3 files changed, 194 insertions(+), 87 deletions(-) diff --git a/src/components/panel/MainLibrary.tsx b/src/components/panel/MainLibrary.tsx index 7b8e57c125..244e1851ff 100644 --- a/src/components/panel/MainLibrary.tsx +++ b/src/components/panel/MainLibrary.tsx @@ -4,6 +4,7 @@ import { open } from '@tauri-apps/plugin-shell'; import { AlertTriangle, Check, + CheckCheck, Folder, FolderInput, Home, @@ -13,6 +14,7 @@ import { Search, Users, SlidersHorizontal, + X, } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useTranslation } from 'react-i18next'; @@ -96,6 +98,8 @@ export default function MainLibrary(props: MainLibraryProps) { const [isBusyDelayed, setIsBusyDelayed] = useState(false); const [isProgressHovered, setIsProgressHovered] = useState(false); + const [selectionModeActive, setSelectionModeActive] = useState(false); + const setLibrary = useLibraryStore((state) => state.setLibrary); const searchCriteria = useLibraryStore((state) => state.searchCriteria); const translatedRatingFilterOptions = useMemo( @@ -412,102 +416,154 @@ export default function MainLibrary(props: MainLibraryProps) { return (
setIsProgressHovered(true)} onMouseLeave={() => setIsProgressHovered(false)} > -
- {t('library.header.title')} - {!props.isAndroid && ( -
- {props.currentFolderPath ? ( - {props.currentFolderPath} - ) : ( -

+ {selectionModeActive ? ( +
+
+ + {props.multiSelectedPaths.length > 0 + ? t('library.header.selectedCount', { count: props.multiSelectedPaths.length }) + : t('library.header.selectItems')} + +
+
+ {props.multiSelectedPaths.length < props.imageList.length && ( + )} -
- -
0 - ? 'max-w-xs opacity-100' - : 'max-w-0 opacity-0' - }`} + {props.multiSelectedPaths.length === props.imageList.length && props.imageList.length > 0 && ( + + )} + +
+
+ ) : ( + <> +
+ {t('library.header.title')} + {!props.isAndroid && ( +
+ {props.currentFolderPath ? ( + {props.currentFolderPath} + ) : ( +

+ )} +
+ +
0 + ? 'max-w-xs opacity-100' + : 'max-w-0 opacity-0' + }`} + > + + ({props.thumbnailProgress?.current ?? 0}/{props.thumbnailProgress?.total ?? 0}) + +
+
-
+ )}
- )} -
-
- {props.importState.status === Status.Importing && ( - - - - {t('library.import.progress', { - current: props.importState.progress?.current, - total: props.importState.progress?.total, - })} - - - )} - {props.importState.status === Status.Success && ( - - - {t('library.import.complete')} - - )} - {props.importState.status === Status.Error && ( - - - {t('library.import.failed')} - - )} - - - {!props.isAndroid && ( - <> +
+ {props.importState.status === Status.Importing && ( + + + + {t('library.import.progress', { + current: props.importState.progress?.current, + total: props.importState.progress?.total, + })} + + + )} + {props.importState.status === Status.Success && ( + + + {t('library.import.complete')} + + )} + {props.importState.status === Status.Error && ( + + + {t('library.import.failed')} + + )} + + + + {!props.isAndroid && ( + <> + + + )} - - )} - -
+
+ + )}
{props.imageList.length > 0 ? ( - + ) : props.isIndexing || props.aiModelDownloadStatus || props.importState.status === Status.Importing ? (
diff --git a/src/components/panel/library/LibraryGrid.tsx b/src/components/panel/library/LibraryGrid.tsx index 642a8c9c75..c95573e99f 100644 --- a/src/components/panel/library/LibraryGrid.tsx +++ b/src/components/panel/library/LibraryGrid.tsx @@ -430,6 +430,7 @@ export default function LibraryGrid(props: any) { onContextMenu, onImageClick, onImageDoubleClick, + selectionModeActive: props.selectionModeActive, thumbnailAspectRatio, onImageLoad: handleImageLoad, imageRatings, @@ -450,6 +451,7 @@ export default function LibraryGrid(props: any) { onContextMenu, onImageClick, onImageDoubleClick, + props.selectionModeActive, thumbnailAspectRatio, handleImageLoad, imageRatings, @@ -464,7 +466,7 @@ export default function LibraryGrid(props: any) {
); @@ -493,7 +495,7 @@ export default function LibraryGrid(props: any) {
diff --git a/src/components/panel/library/LibraryItems.tsx b/src/components/panel/library/LibraryItems.tsx index 34c109a41b..a7313265b5 100644 --- a/src/components/panel/library/LibraryItems.tsx +++ b/src/components/panel/library/LibraryItems.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; -import { Image as ImageIcon, Folder, FolderOpen, Star as StarIcon, SlidersHorizontal, CloudOff } from 'lucide-react'; +import { Check, Image as ImageIcon, Folder, FolderOpen, Star as StarIcon, SlidersHorizontal, CloudOff } from 'lucide-react'; import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; import { COLOR_LABELS, Color } from '../../../utils/adjustments'; @@ -7,6 +7,7 @@ import { ThumbnailAspectRatio, ImageFile, ExifOverlay } from '../../ui/AppProper import Text from '../../ui/Text'; import { TextColors, TextVariants, TextWeights, TEXT_COLOR_KEYS } from '../../../types/typography'; import { ColumnWidths } from '../MainLibrary'; +import { useLibraryStore } from '../../../store/useLibraryStore'; import { useProcessStore } from '../../../store/useProcessStore'; import { useSettingsStore } from '../../../store/useSettingsStore'; import { IconAperture, IconFocalLength, IconIso, IconShutter } from '../editor/ExifIcons'; @@ -23,6 +24,7 @@ const ThumbnailComponent = ({ onContextMenu, onImageClick, onImageDoubleClick, + selectionModeActive, onLoad, path, rating, @@ -148,11 +150,29 @@ const ThumbnailComponent = ({ className="aspect-square bg-surface rounded-md overflow-hidden cursor-pointer group relative flex flex-col transition-all duration-150 transform-gpu [-webkit-mask-image:-webkit-radial-gradient(white,black)]" onClick={(e: any) => { e.stopPropagation(); - onImageClick(path, e); + if (selectionModeActive) { + const state = useLibraryStore.getState(); + const newSet = new Set(state.multiSelectedPaths); + if (newSet.has(path)) newSet.delete(path); + else newSet.add(path); + state.setLibrary({ multiSelectedPaths: Array.from(newSet) }); + } else { + onImageClick(path, e); + } }} onContextMenu={(e: any) => onContextMenu(e, path)} onDoubleClick={() => onImageDoubleClick(path)} > + {selectionModeActive && ( +
+ {isSelected && } +
+ )}
{layers.length > 0 && (
@@ -422,6 +442,7 @@ const ListItemComponent = ({ onContextMenu, onImageClick, onImageDoubleClick, + selectionModeActive, onLoad, path, rating, @@ -554,11 +575,36 @@ const ListItemComponent = ({ className={`flex items-center w-full h-full border-b border-border-color/30 cursor-pointer transition-colors duration-150 ${stateClass}`} onClick={(e: any) => { e.stopPropagation(); - onImageClick(path, e); + if (selectionModeActive) { + const state = useLibraryStore.getState(); + const newSet = new Set(state.multiSelectedPaths); + if (newSet.has(path)) newSet.delete(path); + else newSet.add(path); + state.setLibrary({ multiSelectedPaths: Array.from(newSet) }); + } else { + onImageClick(path, e); + } }} onContextMenu={(e: any) => onContextMenu(e, path)} onDoubleClick={() => onImageDoubleClick(path)} > + {selectionModeActive && ( +
+
+ {isSelected && } +
+
+ )}
Date: Mon, 13 Jul 2026 18:07:46 +0200 Subject: [PATCH 5/6] fix touch multi-select tick marks --- src/components/panel/MainLibrary.tsx | 20 ++++---- src/components/panel/library/LibraryItems.tsx | 47 +++++++++++++------ src/i18n/locales/de.json | 5 ++ src/i18n/locales/en.json | 5 ++ 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/src/components/panel/MainLibrary.tsx b/src/components/panel/MainLibrary.tsx index 244e1851ff..977a48a1fa 100644 --- a/src/components/panel/MainLibrary.tsx +++ b/src/components/panel/MainLibrary.tsx @@ -416,23 +416,21 @@ export default function MainLibrary(props: MainLibraryProps) { return (
setIsProgressHovered(true)} onMouseLeave={() => setIsProgressHovered(false)} > {selectionModeActive ? ( -
-
+ <> +
- {props.multiSelectedPaths.length > 0 - ? t('library.header.selectedCount', { count: props.multiSelectedPaths.length }) - : t('library.header.selectItems')} + {t('library.header.selectItems')}
-
+
{props.multiSelectedPaths.length < props.imageList.length && ( )}
-
+ ) : ( <>
diff --git a/src/components/panel/library/LibraryItems.tsx b/src/components/panel/library/LibraryItems.tsx index a7313265b5..27bdadbbf7 100644 --- a/src/components/panel/library/LibraryItems.tsx +++ b/src/components/panel/library/LibraryItems.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; -import { Check, Image as ImageIcon, Folder, FolderOpen, Star as StarIcon, SlidersHorizontal, CloudOff } from 'lucide-react'; +import { Image as ImageIcon, Folder, FolderOpen, Star as StarIcon, SlidersHorizontal, CloudOff } from 'lucide-react'; import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; import { COLOR_LABELS, Color } from '../../../utils/adjustments'; @@ -20,11 +20,11 @@ interface ImageLayer { const ThumbnailComponent = ({ isActive, - isSelected, onContextMenu, onImageClick, onImageDoubleClick, selectionModeActive, + isSelected, onLoad, path, rating, @@ -155,7 +155,8 @@ const ThumbnailComponent = ({ const newSet = new Set(state.multiSelectedPaths); if (newSet.has(path)) newSet.delete(path); else newSet.add(path); - state.setLibrary({ multiSelectedPaths: Array.from(newSet) }); + const newArr = Array.from(newSet); + state.setLibrary({ multiSelectedPaths: newArr }); } else { onImageClick(path, e); } @@ -166,11 +167,21 @@ const ThumbnailComponent = ({ {selectionModeActive && (
- {isSelected && } + + +
)}
@@ -438,11 +449,11 @@ const ThumbnailComponent = ({ const ListItemComponent = ({ isActive, - isSelected, onContextMenu, onImageClick, onImageDoubleClick, selectionModeActive, + isSelected, onLoad, path, rating, @@ -580,7 +591,8 @@ const ListItemComponent = ({ const newSet = new Set(state.multiSelectedPaths); if (newSet.has(path)) newSet.delete(path); else newSet.add(path); - state.setLibrary({ multiSelectedPaths: Array.from(newSet) }); + const newArr = Array.from(newSet); + state.setLibrary({ multiSelectedPaths: newArr }); } else { onImageClick(path, e); } @@ -589,19 +601,24 @@ const ListItemComponent = ({ onDoubleClick={() => onImageDoubleClick(path)} > {selectionModeActive && ( -
+
- {isSelected && } + + +
)} diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 898c594c5a..7b385c64ff 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -879,6 +879,7 @@ "clearSearch": "Suche löschen", "collapse": "Zuklappen", "expand": "Aufklappen", + "select": "Auswählen", "sortFolders": "Ordner sortieren" } }, @@ -895,6 +896,10 @@ } }, "header": { + "cancel": "Abbrechen", + "deselectAll": "Alle abwählen", + "selectAll": "Alle auswählen", + "selectItems": "Bilder auswählen", "search": { "addFilterOrSearch": "Filter oder Suche hinzufügen...", "indexingImages": "Bilder werden indiziert...", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 06933c11e4..48002b0995 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -879,6 +879,7 @@ "clearSearch": "Clear search", "collapse": "Collapse", "expand": "Expand", + "select": "Select", "sortFolders": "Sort Folders" } }, @@ -895,6 +896,10 @@ } }, "header": { + "cancel": "Cancel", + "deselectAll": "Deselect All", + "selectAll": "Select All", + "selectItems": "Select items", "search": { "addFilterOrSearch": "Add filter or search...", "indexingImages": "Indexing Images...", From 035b57fd16f395d2f1bc3194470133fe43dbc03b Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 13 Jul 2026 18:52:28 +0200 Subject: [PATCH 6/6] make selection menu only visible on Android --- src/components/panel/MainLibrary.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/panel/MainLibrary.tsx b/src/components/panel/MainLibrary.tsx index 977a48a1fa..b3db0ef9dd 100644 --- a/src/components/panel/MainLibrary.tsx +++ b/src/components/panel/MainLibrary.tsx @@ -420,7 +420,7 @@ export default function MainLibrary(props: MainLibraryProps) { onMouseEnter={() => setIsProgressHovered(true)} onMouseLeave={() => setIsProgressHovered(false)} > - {selectionModeActive ? ( + {selectionModeActive && props.isAndroid ? ( <>
@@ -530,13 +530,15 @@ export default function MainLibrary(props: MainLibraryProps) { editedStatusOptions={translatedEditedStatusOptions} sortOptions={translatedSortOptions} /> - + {props.isAndroid && ( + + )} {!props.isAndroid && ( <>