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
|
||||
* the green tint overlay. */
|
||||
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
|
||||
* subscribed-to here so we don't have N thumbnails each running a
|
||||
* per-keystroke selector. Empty string disables the highlight. */
|
||||
@@ -91,6 +99,8 @@ function PhotoThumbnailImpl({
|
||||
fill = false,
|
||||
isSelected,
|
||||
isInActiveHeap = false,
|
||||
hideDiscardedTint = false,
|
||||
hideActiveHeapTint = false,
|
||||
searchQuery = '',
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
@@ -267,9 +277,11 @@ function PhotoThumbnailImpl({
|
||||
className={cn(
|
||||
'h-full w-full object-cover transition-opacity duration-200',
|
||||
imageLoaded ? 'opacity-100' : 'opacity-0',
|
||||
// Discarded photos fade out + desaturate so the trash section
|
||||
// reads as a trash section, not just another grid view.
|
||||
photo.is_discarded && 'opacity-50 grayscale'
|
||||
// Discarded photos fade out + desaturate so they read as
|
||||
// trash when mixed into a normal timeline. Suppressed when
|
||||
// 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}
|
||||
onError={handleImageError}
|
||||
@@ -310,7 +322,7 @@ function PhotoThumbnailImpl({
|
||||
* both selected AND in the active heap shows both tints. Never
|
||||
* tinted on hover so the keyboard-driven selection stays
|
||||
* distinguishable from the mouse-driven hover. */}
|
||||
{isInActiveHeap && (
|
||||
{isInActiveHeap && !hideActiveHeapTint && (
|
||||
<div className="pointer-events-none absolute inset-0 bg-emerald-500/40" />
|
||||
)}
|
||||
{isSelected && (
|
||||
|
||||
@@ -173,6 +173,8 @@ export function Timeline() {
|
||||
const sortBy = useFilterStore((s) => s.sortBy)
|
||||
const groupBy = useFilterStore((s) => s.groupBy)
|
||||
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
|
||||
// PhotoThumbnail as a prop. Previously every thumbnail had its own
|
||||
// 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
|
||||
// thumbnail). Subscribed once at this level so we don't have hundreds
|
||||
// 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
|
||||
// identity-stable callbacks let it skip re-render on unrelated store
|
||||
@@ -812,6 +820,8 @@ export function Timeline() {
|
||||
fill
|
||||
isSelected={selectedPhotos.includes(photo.id)}
|
||||
isInActiveHeap={activeHeapMembers.has(photo.id)}
|
||||
hideDiscardedTint={hideDiscardedTint}
|
||||
hideActiveHeapTint={hideActiveHeapTint}
|
||||
searchQuery={searchQuery}
|
||||
onClick={handleCellClick}
|
||||
onDoubleClick={handleCellDoubleClick}
|
||||
|
||||
Reference in New Issue
Block a user