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 ? ( + {image.alt} + ) : ( + + )} +
+
) @@ -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} -
- ) - })} -
+ +
+
+ {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} +
+ ) + })} +
+
) } + +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 ( ) },