ui(timeline): drop redundant tints in discarded + active-heap views
When the grid is already filtered to discarded photos or to the active heap, every cell would carry the same tint — the grayscale wash or the green overlay stopped signalling anything and just made thumbnails harder to read. Timeline now suppresses both when the corresponding filter is active; the BR icon badges stay for colorblind readability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,14 @@ interface PhotoThumbnailProps {
|
|||||||
/** True when the photo belongs to the currently active heap — drives
|
/** True when the photo belongs to the currently active heap — drives
|
||||||
* the green tint overlay. */
|
* the green tint overlay. */
|
||||||
isInActiveHeap?: boolean
|
isInActiveHeap?: boolean
|
||||||
|
/** Suppress the desaturate/opacity treatment applied to discarded
|
||||||
|
* photos. Set by the Timeline when the view is already filtered to
|
||||||
|
* discarded only — every cell would otherwise carry the same tint. */
|
||||||
|
hideDiscardedTint?: boolean
|
||||||
|
/** Suppress the green "in active heap" tint overlay. Set when the
|
||||||
|
* view is filtered to the active heap, where every cell would
|
||||||
|
* otherwise be wash-green. */
|
||||||
|
hideActiveHeapTint?: boolean
|
||||||
/** Active text-search query, passed in from Timeline rather than
|
/** Active text-search query, passed in from Timeline rather than
|
||||||
* subscribed-to here so we don't have N thumbnails each running a
|
* subscribed-to here so we don't have N thumbnails each running a
|
||||||
* per-keystroke selector. Empty string disables the highlight. */
|
* per-keystroke selector. Empty string disables the highlight. */
|
||||||
@@ -91,6 +99,8 @@ function PhotoThumbnailImpl({
|
|||||||
fill = false,
|
fill = false,
|
||||||
isSelected,
|
isSelected,
|
||||||
isInActiveHeap = false,
|
isInActiveHeap = false,
|
||||||
|
hideDiscardedTint = false,
|
||||||
|
hideActiveHeapTint = false,
|
||||||
searchQuery = '',
|
searchQuery = '',
|
||||||
onClick,
|
onClick,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
@@ -267,9 +277,11 @@ function PhotoThumbnailImpl({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'h-full w-full object-cover transition-opacity duration-200',
|
'h-full w-full object-cover transition-opacity duration-200',
|
||||||
imageLoaded ? 'opacity-100' : 'opacity-0',
|
imageLoaded ? 'opacity-100' : 'opacity-0',
|
||||||
// Discarded photos fade out + desaturate so the trash section
|
// Discarded photos fade out + desaturate so they read as
|
||||||
// reads as a trash section, not just another grid view.
|
// trash when mixed into a normal timeline. Suppressed when
|
||||||
photo.is_discarded && 'opacity-50 grayscale'
|
// the view is already filtered to discarded only (every
|
||||||
|
// cell would get the same tint — pure noise).
|
||||||
|
photo.is_discarded && !hideDiscardedTint && 'opacity-50 grayscale'
|
||||||
)}
|
)}
|
||||||
onLoad={handleImageLoad}
|
onLoad={handleImageLoad}
|
||||||
onError={handleImageError}
|
onError={handleImageError}
|
||||||
@@ -310,7 +322,7 @@ function PhotoThumbnailImpl({
|
|||||||
* both selected AND in the active heap shows both tints. Never
|
* both selected AND in the active heap shows both tints. Never
|
||||||
* tinted on hover so the keyboard-driven selection stays
|
* tinted on hover so the keyboard-driven selection stays
|
||||||
* distinguishable from the mouse-driven hover. */}
|
* distinguishable from the mouse-driven hover. */}
|
||||||
{isInActiveHeap && (
|
{isInActiveHeap && !hideActiveHeapTint && (
|
||||||
<div className="pointer-events-none absolute inset-0 bg-emerald-500/40" />
|
<div className="pointer-events-none absolute inset-0 bg-emerald-500/40" />
|
||||||
)}
|
)}
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
|
|||||||
@@ -173,6 +173,8 @@ export function Timeline() {
|
|||||||
const sortBy = useFilterStore((s) => s.sortBy)
|
const sortBy = useFilterStore((s) => s.sortBy)
|
||||||
const groupBy = useFilterStore((s) => s.groupBy)
|
const groupBy = useFilterStore((s) => s.groupBy)
|
||||||
const currentSection = useFilterStore((s) => s.currentSection)
|
const currentSection = useFilterStore((s) => s.currentSection)
|
||||||
|
const flag = useFilterStore((s) => s.flag)
|
||||||
|
const filterHeapId = useFilterStore((s) => s.heapId)
|
||||||
// Subscribed once at this level and passed down to each
|
// Subscribed once at this level and passed down to each
|
||||||
// PhotoThumbnail as a prop. Previously every thumbnail had its own
|
// PhotoThumbnail as a prop. Previously every thumbnail had its own
|
||||||
// subscription, multiplying keystroke renders by the row count.
|
// subscription, multiplying keystroke renders by the row count.
|
||||||
@@ -231,7 +233,13 @@ export function Timeline() {
|
|||||||
// Membership in the active heap (drives the green tint on each
|
// Membership in the active heap (drives the green tint on each
|
||||||
// thumbnail). Subscribed once at this level so we don't have hundreds
|
// thumbnail). Subscribed once at this level so we don't have hundreds
|
||||||
// of thumbnails each subscribing to the same query.
|
// of thumbnails each subscribing to the same query.
|
||||||
const { memberIds: activeHeapMembers } = useActiveHeapMembers()
|
const { activeHeap, memberIds: activeHeapMembers } = useActiveHeapMembers()
|
||||||
|
// When the view is already filtered to a specific attribute, every
|
||||||
|
// thumbnail would carry the same indicator — redundant visual noise.
|
||||||
|
// Pass these down so PhotoThumbnail can skip the tint in those cases.
|
||||||
|
const hideDiscardedTint = flag === 'discarded'
|
||||||
|
const hideActiveHeapTint =
|
||||||
|
filterHeapId != null && activeHeap != null && filterHeapId === activeHeap.id
|
||||||
|
|
||||||
// Stable cell handlers. PhotoThumbnail is wrapped in React.memo so
|
// Stable cell handlers. PhotoThumbnail is wrapped in React.memo so
|
||||||
// identity-stable callbacks let it skip re-render on unrelated store
|
// identity-stable callbacks let it skip re-render on unrelated store
|
||||||
@@ -812,6 +820,8 @@ export function Timeline() {
|
|||||||
fill
|
fill
|
||||||
isSelected={selectedPhotos.includes(photo.id)}
|
isSelected={selectedPhotos.includes(photo.id)}
|
||||||
isInActiveHeap={activeHeapMembers.has(photo.id)}
|
isInActiveHeap={activeHeapMembers.has(photo.id)}
|
||||||
|
hideDiscardedTint={hideDiscardedTint}
|
||||||
|
hideActiveHeapTint={hideActiveHeapTint}
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
onClick={handleCellClick}
|
onClick={handleCellClick}
|
||||||
onDoubleClick={handleCellDoubleClick}
|
onDoubleClick={handleCellDoubleClick}
|
||||||
|
|||||||
Reference in New Issue
Block a user