Skip to content

Commit

Permalink
Make thumbnails clickable (#348)
Browse files Browse the repository at this point in the history
* Prepare image carousel to handle links

* Link all thumbnails to detail views
  • Loading branch information
annavik authored Feb 11, 2024
1 parent db6fcc4 commit a2a27cf
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 69 deletions.
135 changes: 83 additions & 52 deletions ui/src/design-system/components/image-carousel/image-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,66 @@ import {
IconButtonTheme,
} from 'design-system/components/icon-button/icon-button'
import { Icon, IconTheme, IconType } from 'design-system/components/icon/icon'
import { useEffect, useRef, useState } from 'react'
import { ReactNode, useEffect, useRef, useState } from 'react'
import styles from './image-carousel.module.scss'
import { CarouselTheme } from './types'
import { getImageBoxStyles, getPlaceholderStyles } from './utils'
import { Link } from 'react-router-dom'

interface ImageCarouselProps {
autoPlay?: boolean
images: {
src: string
alt?: string
}[]
theme?: CarouselTheme
autoPlay?: boolean
size?: {
width: string | number
ratio: number
}
theme?: CarouselTheme
to?: string
}

export const ImageCarousel = ({
images,
theme = CarouselTheme.Default,
autoPlay,
images,
size,
theme = CarouselTheme.Default,
to,
}: ImageCarouselProps) => {
if (images.length <= 1) {
return <BasicImageCarousel image={images[0]} theme={theme} size={size} />
return (
<BasicImageCarousel image={images[0]} size={size} theme={theme} to={to} />
)
}

return (
<MultiImageCarousel
images={images}
theme={theme}
autoPlay={autoPlay}
images={images}
size={size}
theme={theme}
to={to}
/>
)
}

const BasicImageCarousel = ({
image,
theme,
size,
theme,
to,
}: {
image?: {
src: string
alt?: string
}
theme: CarouselTheme
size?: {
width: string | number
ratio: number
}
theme: CarouselTheme
to?: string
}) => (
<div className={styles.container}>
<div
Expand All @@ -66,28 +74,31 @@ const BasicImageCarousel = ({
style={getImageBoxStyles(size?.width)}
>
<div style={getPlaceholderStyles(size?.ratio)} />
<div className={classNames(styles.slide, styles.visible)}>
{image ? (
<img src={image.src} alt={image.alt} className={styles.image} />
) : (
<Icon
type={IconType.Photograph}
theme={IconTheme.Neutral}
size={16}
/>
)}
</div>
<ConditionalLink to={to}>
<div className={classNames(styles.slide, styles.visible)}>
{image ? (
<img src={image.src} alt={image.alt} className={styles.image} />
) : (
<Icon
type={IconType.Photograph}
theme={IconTheme.Neutral}
size={16}
/>
)}
</div>
</ConditionalLink>
</div>
</div>
)

const DURATION = 10000 // Change image every 10 second

const MultiImageCarousel = ({
images,
theme,
autoPlay,
images,
size,
theme,
to,
}: ImageCarouselProps) => {
const [paused, setPaused] = useState(false)
const [slideIndex, setSlideIndex] = useState(0)
Expand Down Expand Up @@ -154,35 +165,41 @@ const MultiImageCarousel = ({
onClick={() => showPrev(slideIndex)}
/>
</div>
<div
className={classNames(styles.imageBox, {
[styles.light]: theme === CarouselTheme.Light,
})}
style={getImageBoxStyles(size?.width)}
>
<div style={getPlaceholderStyles(size?.ratio)} />
{images.map((image, index) => {
const render =
index === 0 || // Always render first slide
index === images.length - 1 || // Always render last image
Math.abs(index - slideIndex) <= 1 // Render nearby slides

if (!render) {
return
}

return (
<div
key={index}
className={classNames(styles.slide, {
[styles.visible]: index === slideIndex,
})}
>
<img src={image.src} alt={image.alt} className={styles.image} />
</div>
)
})}
</div>
<ConditionalLink to={to}>
<div
className={classNames(styles.imageBox, {
[styles.light]: theme === CarouselTheme.Light,
})}
style={getImageBoxStyles(size?.width)}
>
<div style={getPlaceholderStyles(size?.ratio)} />
{images.map((image, index) => {
const render =
index === 0 || // Always render first slide
index === images.length - 1 || // Always render last image
Math.abs(index - slideIndex) <= 1 // Render nearby slides

if (!render) {
return
}

return (
<div
key={index}
className={classNames(styles.slide, {
[styles.visible]: index === slideIndex,
})}
>
<img
src={image.src}
alt={image.alt}
className={styles.image}
/>
</div>
)
})}
</div>
</ConditionalLink>
<div
className={classNames(styles.control, {
[styles.visible]: paused,
Expand Down Expand Up @@ -211,3 +228,17 @@ const MultiImageCarousel = ({
</div>
)
}

const ConditionalLink = ({
to,
children,
}: {
to?: string
children: ReactNode
}) => {
if (!to) {
return <>{children}</>
}

return <Link to={to}>{children}</Link>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { ImageCellTheme } from '../types'
import styles from './image-table-cell.module.scss'

interface ImageTableCellProps {
autoPlay?: boolean
images: {
src: string
alt?: string
}[]
theme?: ImageCellTheme
autoPlay?: boolean
to?: string
}

export const ImageTableCell = (props: ImageTableCellProps) => (
Expand Down
30 changes: 14 additions & 16 deletions ui/src/pages/collection-details/capture-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,22 @@ export const columns: (projectId: string) => TableColumn<Capture>[] = (
name: translate(STRING.FIELD_LABEL_THUMBNAIL),
renderCell: (item: Capture, rowIndex: number) => {
const isOddRow = rowIndex % 2 == 0
const detailsRoute = getAppRoute({
to: APP_ROUTES.SESSION_DETAILS({
projectId: projectId,
sessionId: item.sessionId,
}),
filters: {
capture: item.id,
},
})

return (
<Link
to={getAppRoute({
to: APP_ROUTES.SESSION_DETAILS({
projectId: projectId,
sessionId: item.sessionId,
}),
filters: {
capture: item.id,
},
})}
>
<ImageTableCell
images={[{ src: item.src }]}
theme={isOddRow ? ImageCellTheme.Default : ImageCellTheme.Light}
/>
</Link>
<ImageTableCell
images={[{ src: item.src }]}
theme={isOddRow ? ImageCellTheme.Default : ImageCellTheme.Light}
to={detailsRoute}
/>
)
},
},
Expand Down
8 changes: 8 additions & 0 deletions ui/src/pages/occurrences/occurrence-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,19 @@ export const columns: (projectId: string) => TableColumn<Occurrence>[] = (
},
renderCell: (item: Occurrence, rowIndex: number) => {
const isOddRow = rowIndex % 2 == 0
const detailsRoute = getAppRoute({
to: APP_ROUTES.OCCURRENCE_DETAILS({
projectId,
occurrenceId: item.id,
}),
keepSearchParams: true,
})

return (
<ImageTableCell
images={item.images}
theme={isOddRow ? ImageCellTheme.Default : ImageCellTheme.Light}
to={detailsRoute}
/>
)
},
Expand Down
2 changes: 2 additions & 0 deletions ui/src/pages/sessions/session-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ export const columns: (projectId: string) => TableColumn<Session>[] = (
},
renderCell: (item: Session, rowIndex: number) => {
const isOddRow = rowIndex % 2 == 0
const detailsRoute = APP_ROUTES.SESSION_DETAILS({ projectId, sessionId: item.id })

return (
<ImageTableCell
images={item.exampleCaptures}
to={detailsRoute}
theme={isOddRow ? ImageCellTheme.Default : ImageCellTheme.Light}
/>
)
Expand Down
5 changes: 5 additions & 0 deletions ui/src/pages/species/species-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ export const columns: (projectId: string) => TableColumn<Species>[] = (
},
renderCell: (item: Species, rowIndex: number) => {
const isOddRow = rowIndex % 2 == 0
const detailsRoute = getAppRoute({
to: APP_ROUTES.SPECIES_DETAILS({ projectId, speciesId: item.id }),
keepSearchParams: true,
})

return (
<ImageTableCell
images={item.images}
theme={isOddRow ? ImageCellTheme.Default : ImageCellTheme.Light}
to={detailsRoute}
/>
)
},
Expand Down

0 comments on commit a2a27cf

Please sign in to comment.