From 7a4d3a885044054826c4f8ca995ef3df329748d0 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 13 Jun 2024 23:07:13 +0100 Subject: [PATCH 1/2] fix: impl getRouteDuration for Route type --- src/api/derived.ts | 9 +-- src/api/route.ts | 27 ++------ src/components/RouteCard.tsx | 13 ++-- src/components/Timeline.tsx | 14 ++--- src/pages/dashboard/components/RouteList.tsx | 17 +++-- src/types.d.ts | 65 +++++++++++++------- src/utils/date.ts | 19 +++--- 7 files changed, 78 insertions(+), 86 deletions(-) diff --git a/src/api/derived.ts b/src/api/derived.ts index 0340b476..6d834068 100644 --- a/src/api/derived.ts +++ b/src/api/derived.ts @@ -1,4 +1,5 @@ import type { Route } from '~/types' +import { getRouteDuration } from '~/utils/date' export interface GPSPathPoint { t: number @@ -106,9 +107,7 @@ const generateTimelineEvents = ( route: Route, events: DriveEvent[], ): TimelineEvent[] => { - const routeDuration = - route.segment_end_times[route.segment_end_times.length - 1] - - route.segment_start_times[0] + const routeDuration = getRouteDuration(route)?.asMilliseconds() ?? 0 // sort events by timestamp events.sort((a, b) => { @@ -218,9 +217,7 @@ const generateTimelineStatistics = ( return { engagedDuration, userFlags, - duration: - route.segment_end_times[route.segment_end_times.length - 1] - - route.segment_start_times[0], + duration: getRouteDuration(route)?.asMilliseconds() ?? 0, } } diff --git a/src/api/route.ts b/src/api/route.ts index f6658ea3..60bd0852 100644 --- a/src/api/route.ts +++ b/src/api/route.ts @@ -1,13 +1,12 @@ import { fetcher } from '.' import { BASE_URL } from './config' -import type { Device, Route } from '~/types' +import type { Device, Route, RouteShareSignature } from '~/types' export class RouteName { // dongle ID date str // 0123456789abcdef|2023-02-15--15-25-00 - static readonly regex = - /^([0-9a-f]{16})\|(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2})$/ + static readonly regex = /^([0-9a-f]{16})\|(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2})$/ static readonly regexGroup = { dongleId: 1, dateStr: 2, @@ -18,10 +17,7 @@ export class RouteName { if (!match) { return null } - return new RouteName( - match[RouteName.regexGroup.dongleId], - match[RouteName.regexGroup.dateStr], - ) + return new RouteName(match[RouteName.regexGroup.dongleId], match[RouteName.regexGroup.dateStr]) } readonly dongleId: Device['dongle_id'] @@ -40,27 +36,16 @@ export class RouteName { export const getRoute = (routeName: Route['fullname']): Promise => fetcher(`/v1/route/${routeName}/`) -interface RouteShareSignature extends Record { - exp: NonNullable - sig: NonNullable -} - -export const getRouteShareSignature = ( - routeName: string, -): Promise => +export const getRouteShareSignature = (routeName: string): Promise => fetcher(`/v1/route/${routeName}/share_signature`) export const createQCameraStreamUrl = ( routeName: Route['fullname'], signature: RouteShareSignature, ): string => - `${BASE_URL}/v1/route/${routeName}/qcamera.m3u8?${new URLSearchParams( - signature, - ).toString()}` + `${BASE_URL}/v1/route/${routeName}/qcamera.m3u8?${new URLSearchParams(signature).toString()}` -export const getQCameraStreamUrl = ( - routeName: Route['fullname'], -): Promise => +export const getQCameraStreamUrl = (routeName: Route['fullname']): Promise => getRouteShareSignature(routeName).then((signature) => createQCameraStreamUrl(routeName, signature), ) diff --git a/src/components/RouteCard.tsx b/src/components/RouteCard.tsx index a12f3b71..0b5aeef3 100644 --- a/src/components/RouteCard.tsx +++ b/src/components/RouteCard.tsx @@ -7,18 +7,15 @@ import Icon from '~/components/material/Icon' import RouteStaticMap from '~/components/RouteStaticMap' import RouteStatistics from '~/components/RouteStatistics' -import type { Route } from '~/types' +import type { RouteSegments } from '~/types' -const RouteHeader = (props: { route: Route }) => { +const RouteHeader = (props: { route: RouteSegments }) => { const startTime = () => dayjs(props.route.segment_start_times[0]) const endTime = () => - dayjs( - props.route.segment_end_times[props.route.segment_end_times.length - 1], - ) + dayjs(props.route.segment_end_times[props.route.segment_end_times.length - 1]) const headline = () => startTime().format('ddd, MMM D, YYYY') - const subhead = () => - `${startTime().format('h:mm A')} to ${endTime().format('h:mm A')}` + const subhead = () => `${startTime().format('h:mm A')} to ${endTime().format('h:mm A')}` return ( { } interface RouteCardProps { - route: Route + route: RouteSegments } const RouteCard: VoidComponent = (props) => { diff --git a/src/components/Timeline.tsx b/src/components/Timeline.tsx index 6b12c78a..70f1ce3b 100644 --- a/src/components/Timeline.tsx +++ b/src/components/Timeline.tsx @@ -5,6 +5,7 @@ import clsx from 'clsx' import { TimelineEvent, getTimelineEvents } from '~/api/derived' import { getRoute } from '~/api/route' import type { Route } from '~/types' +import { getRouteDuration } from '~/utils/date' function renderTimelineEvents( route: Route | undefined, @@ -12,10 +13,7 @@ function renderTimelineEvents( ) { if (!route) return null - const duration = - route.segment_end_times[route.segment_end_times.length - 1] - - route.segment_start_times[0] - + const duration = getRouteDuration(route)?.asMilliseconds() ?? 0 return ( {(event) => { @@ -99,12 +97,8 @@ function renderMarker(route: Route | undefined, seekTime: number | undefined) { if (!route) return null if (seekTime === undefined) return null - const duration = - route.segment_end_times[route.segment_end_times.length - 1] - - route.segment_start_times[0] - - const offsetPct = (seekTime / (duration / 1000)) * 100 - + const duration = getRouteDuration(route)?.asSeconds() ?? 0 + const offsetPct = (seekTime / duration) * 100 return (
[] = [] +const pages: Promise[] = [] const RouteList: VoidComponent = (props) => { - const endpoint = () => - `/v1/devices/${props.dongleId}/routes_segments?limit=${PAGE_SIZE}` - const getKey = (previousPageData?: Route[]): string | undefined => { + const endpoint = () => `/v1/devices/${props.dongleId}/routes_segments?limit=${PAGE_SIZE}` + const getKey = (previousPageData?: RouteSegments[]): string | undefined => { if (!previousPageData) return endpoint() if (previousPageData.length === 0) return undefined - const lastRoute = previousPageData[previousPageData.length - 1] - const lastSegmentEndTime = - lastRoute.segment_start_times[lastRoute.segment_start_times.length - 1] + const lastSegmentEndTime = previousPageData.at(-1)!.segment_start_times.at(-1)! return `${endpoint()}&end=${lastSegmentEndTime - 1}` } - const getPage = (page: number): Promise => { + const getPage = (page: number): Promise => { if (!pages[page]) { // eslint-disable-next-line no-async-promise-executor pages[page] = new Promise(async (resolve) => { const previousPageData = page > 0 ? await getPage(page - 1) : undefined const key = getKey(previousPageData) - resolve(key ? fetcher(key) : []) + resolve(key ? fetcher(key) : []) }) } return pages[page] diff --git a/src/types.d.ts b/src/types.d.ts index 7c8e51f8..cc9d76e4 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -46,35 +46,56 @@ export enum SegmentDataSource { } export interface Route { - fullname: string + can?: boolean + create_time: number + devicetype: number dongle_id: string - user_id: string + end_lat?: number + end_lng?: number + end_time?: string + fullname: string + git_branch?: string + git_commit?: string + git_dirty?: boolean + git_remote?: string + hpgps?: boolean + init_logmonotime?: number is_public: boolean - url: string - create_time: number - segment_numbers: number[] - segment_start_times: number[] - segment_end_times: number[] length?: number - can?: boolean - hpgps?: boolean - radar?: boolean - devicetype: number + maxcamera: number + maxdcamera: number + maxecamera: number + maxlog: number + maxqcamera: number maxqlog: number - procqlog: number - start_time: number - end_time?: number passive?: boolean - version?: string - git_commit?: string - git_branch?: string - git_remote?: string - git_dirty?: boolean platform?: string + proccamera: number + proclog: number + procqcamera: number + procqlog: number + radar?: boolean + start_time: string + url: string + user_id: string | null + version?: string vin?: string - init_logmonotime?: number - share_exp?: string - share_sig?: string +} + +export interface RouteShareSignature extends Record { + exp: string + sig: string +} + +export interface RouteSegments extends Route { + end_time_utc_millis: number + is_preserved: boolean + segment_end_times: number[] + segment_numbers: number[] + segment_start_times: number[] + share_exp: RouteShareSignature['exp'] + share_sig: RouteShareSignature['sig'] + start_time_utc_millis: number } export interface Clip { diff --git a/src/utils/date.ts b/src/utils/date.ts index 8ce4a064..79cef8c9 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -34,16 +34,17 @@ export const formatDuration = (minutes: number | undefined): string => { return _formatDuration(duration) } +export const getRouteDuration = (route: Route): Duration | undefined => { + if (!route || !route.end_time) return undefined + const startTime = dayjs(route.start_time) + const endTime = dayjs(route.end_time) + return dayjs.duration(endTime.diff(startTime)) +} + export const formatRouteDuration = (route: Route | undefined): string => { - if (!route || !route.segment_start_times || !route.segment_end_times) - return '' - - const startTime = dayjs(route.segment_start_times[0]) - const endTime = dayjs( - route.segment_end_times[route.segment_end_times.length - 1], - ) - const duration = dayjs.duration(endTime.diff(startTime)) - return _formatDuration(duration) + if (!route) return '' + const duration = getRouteDuration(route) + return duration ? _formatDuration(duration) : '' } export const parseDateStr = (dateStr: string): Dayjs => { From c6708f2ddcb6c7b417b1c4f09062ff897e278437 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 13 Jun 2024 23:32:22 +0100 Subject: [PATCH 2/2] new array method --- src/components/RouteCard.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/RouteCard.tsx b/src/components/RouteCard.tsx index 0b5aeef3..91a3a594 100644 --- a/src/components/RouteCard.tsx +++ b/src/components/RouteCard.tsx @@ -11,8 +11,7 @@ import type { RouteSegments } from '~/types' const RouteHeader = (props: { route: RouteSegments }) => { const startTime = () => dayjs(props.route.segment_start_times[0]) - const endTime = () => - dayjs(props.route.segment_end_times[props.route.segment_end_times.length - 1]) + const endTime = () => dayjs(props.route.segment_end_times.at(-1)) const headline = () => startTime().format('ddd, MMM D, YYYY') const subhead = () => `${startTime().format('h:mm A')} to ${endTime().format('h:mm A')}`