diff --git a/ui/index.html b/ui/index.html
index 3efb483a7..f7b4b45e8 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -50,6 +50,16 @@
+
+
+
+
diff --git a/ui/src/design-system/components/image-carousel/image-carousel.tsx b/ui/src/design-system/components/image-carousel/image-carousel.tsx
index 56552ed89..15d751047 100644
--- a/ui/src/design-system/components/image-carousel/image-carousel.tsx
+++ b/ui/src/design-system/components/image-carousel/image-carousel.tsx
@@ -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
+ return (
+
+ )
}
return (
)
}
const BasicImageCarousel = ({
image,
- theme,
size,
+ theme,
+ to,
}: {
image?: {
src: string
alt?: string
}
- theme: CarouselTheme
size?: {
width: string | number
ratio: number
}
+ theme: CarouselTheme
+ to?: string
}) => (
-
- {image ? (
-
![{image.alt}]({image.src})
- ) : (
-
- )}
-
+
+
+ {image ? (
+
![{image.alt}]({image.src})
+ ) : (
+
+ )}
+
+
)
@@ -84,10 +94,11 @@ const BasicImageCarousel = ({
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)
@@ -154,35 +165,41 @@ const MultiImageCarousel = ({
onClick={() => showPrev(slideIndex)}
/>
-
-
- {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 (
-
-
![{image.alt}]({image.src})
-
- )
- })}
-
+
+
+
+ {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 (
+
+
![{image.alt}]({image.src})
+
+ )
+ })}
+
+
)
}
+
+const ConditionalLink = ({
+ to,
+ children,
+}: {
+ to?: string
+ children: ReactNode
+}) => {
+ if (!to) {
+ return <>{children}>
+ }
+
+ return {children}
+}
diff --git a/ui/src/design-system/components/table/image-table-cell/image-table-cell.tsx b/ui/src/design-system/components/table/image-table-cell/image-table-cell.tsx
index d7929fd6e..3f9fdd87c 100644
--- a/ui/src/design-system/components/table/image-table-cell/image-table-cell.tsx
+++ b/ui/src/design-system/components/table/image-table-cell/image-table-cell.tsx
@@ -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) => (
diff --git a/ui/src/pages/collection-details/capture-columns.tsx b/ui/src/pages/collection-details/capture-columns.tsx
index be5bb6157..923645d14 100644
--- a/ui/src/pages/collection-details/capture-columns.tsx
+++ b/ui/src/pages/collection-details/capture-columns.tsx
@@ -20,24 +20,22 @@ export const columns: (projectId: string) => TableColumn[] = (
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 (
-
-
-
+
)
},
},
diff --git a/ui/src/pages/occurrences/occurrence-columns.tsx b/ui/src/pages/occurrences/occurrence-columns.tsx
index 4c0ba9963..e69b2b069 100644
--- a/ui/src/pages/occurrences/occurrence-columns.tsx
+++ b/ui/src/pages/occurrences/occurrence-columns.tsx
@@ -33,11 +33,19 @@ export const columns: (projectId: string) => TableColumn[] = (
},
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 (
)
},
@@ -73,7 +81,7 @@ export const columns: (projectId: string) => TableColumn[] = (
name: translate(STRING.FIELD_LABEL_SESSION),
sortField: 'event',
renderCell: (item: Occurrence) => (
-
+
),
diff --git a/ui/src/pages/session-details/playback/playback-controls/playback-controls.tsx b/ui/src/pages/session-details/playback/playback-controls/playback-controls.tsx
index d07ba9622..4740bc192 100644
--- a/ui/src/pages/session-details/playback/playback-controls/playback-controls.tsx
+++ b/ui/src/pages/session-details/playback/playback-controls/playback-controls.tsx
@@ -10,13 +10,15 @@ import { useState } from 'react'
import { useThreshold } from 'utils/threshold/thresholdContext'
import { CaptureInfo } from '../capture-info/capture-info'
import { CaptureJob } from '../capture-job/capture-job'
-import { useActiveCaptureId } from '../useActiveCapture'
import { PipelinesPicker } from './pipelines-picker'
import styles from './playback-controls.module.scss'
import { StarButton } from './star-button'
-export const PlaybackControls = () => {
- const { activeCaptureId } = useActiveCaptureId()
+export const PlaybackControls = ({
+ activeCaptureId,
+}: {
+ activeCaptureId: string
+}) => {
const { capture, isFetching } = useCaptureDetails(activeCaptureId as string)
const { defaultThreshold, threshold, setThreshold } = useThreshold()
const [showDetails, setShowDetails] = useState(false)
diff --git a/ui/src/pages/session-details/playback/playback.tsx b/ui/src/pages/session-details/playback/playback.tsx
index 844dfa9c3..f654df707 100644
--- a/ui/src/pages/session-details/playback/playback.tsx
+++ b/ui/src/pages/session-details/playback/playback.tsx
@@ -6,7 +6,7 @@ import { CapturePicker } from './capture-picker/capture-picker'
import { Frame } from './frame/frame'
import { PlaybackControls } from './playback-controls/playback-controls'
import styles from './playback.module.scss'
-import { useActiveCapture } from './useActiveCapture'
+import { useActiveCapture, useActiveCaptureId } from './useActiveCapture'
export const Playback = ({ session }: { session: SessionDetails }) => {
const { threshold } = useThreshold()
@@ -21,6 +21,7 @@ export const Playback = ({ session }: { session: SessionDetails }) => {
} = useInfiniteCaptures(session.id, session.captureOffset, threshold)
const { activeCapture, setActiveCapture } = useActiveCapture(captures)
const [showOverlay, setShowOverlay] = useState(false)
+ const { activeCaptureId } = useActiveCaptureId()
if (!session.firstCapture) {
return null
@@ -41,7 +42,9 @@ export const Playback = ({ session }: { session: SessionDetails }) => {
showOverlay={showOverlay}
/>
-
+ {activeCaptureId && (
+
+ )}
diff --git a/ui/src/pages/session-details/playback/useActiveCapture.ts b/ui/src/pages/session-details/playback/useActiveCapture.ts
index e048c7656..6d3052320 100644
--- a/ui/src/pages/session-details/playback/useActiveCapture.ts
+++ b/ui/src/pages/session-details/playback/useActiveCapture.ts
@@ -13,7 +13,7 @@ export const useActiveCaptureId = () => {
const setActiveCaptureId = (captureId: string) => {
searchParams.delete(SEARCH_PARAM_KEY)
searchParams.set(SEARCH_PARAM_KEY, captureId)
- setSearchParams(searchParams)
+ setSearchParams(searchParams, { replace: true })
}
return { activeCaptureId, setActiveCaptureId }
diff --git a/ui/src/pages/session-details/playback/useActiveOccurrences.ts b/ui/src/pages/session-details/playback/useActiveOccurrences.ts
index 383ff6195..79115b79f 100644
--- a/ui/src/pages/session-details/playback/useActiveOccurrences.ts
+++ b/ui/src/pages/session-details/playback/useActiveOccurrences.ts
@@ -12,7 +12,7 @@ export const useActiveOccurrences = () => {
(occurrences: string[]) => {
searchParams.delete(SEARCH_PARAM_KEY)
occurrences.forEach((o) => searchParams.append(SEARCH_PARAM_KEY, o))
- setSearchParams(searchParams)
+ setSearchParams(searchParams, { replace: true })
},
[searchParams, setSearchParams]
)
diff --git a/ui/src/pages/sessions/session-columns.tsx b/ui/src/pages/sessions/session-columns.tsx
index 0a5f40aaf..94e8f4f35 100644
--- a/ui/src/pages/sessions/session-columns.tsx
+++ b/ui/src/pages/sessions/session-columns.tsx
@@ -24,10 +24,12 @@ export const columns: (projectId: string) => TableColumn[] = (
},
renderCell: (item: Session, rowIndex: number) => {
const isOddRow = rowIndex % 2 == 0
+ const detailsRoute = APP_ROUTES.SESSION_DETAILS({ projectId, sessionId: item.id })
return (
)
diff --git a/ui/src/pages/species/species-columns.tsx b/ui/src/pages/species/species-columns.tsx
index fe6e103ca..ca781513a 100644
--- a/ui/src/pages/species/species-columns.tsx
+++ b/ui/src/pages/species/species-columns.tsx
@@ -24,11 +24,16 @@ export const columns: (projectId: string) => TableColumn[] = (
},
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 (
)
},