Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update route API types and fix timeline #27

Merged
merged 2 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/api/derived.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Route } from '~/types'
import { getRouteDuration } from '~/utils/date'

export interface GPSPathPoint {
t: number
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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,
}
}

Expand Down
27 changes: 6 additions & 21 deletions src/api/route.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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']
Expand All @@ -40,27 +36,16 @@ export class RouteName {
export const getRoute = (routeName: Route['fullname']): Promise<Route> =>
fetcher<Route>(`/v1/route/${routeName}/`)

interface RouteShareSignature extends Record<string, string> {
exp: NonNullable<Route['share_exp']>
sig: NonNullable<Route['share_sig']>
}

export const getRouteShareSignature = (
routeName: string,
): Promise<RouteShareSignature> =>
export const getRouteShareSignature = (routeName: string): Promise<RouteShareSignature> =>
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<string> =>
export const getQCameraStreamUrl = (routeName: Route['fullname']): Promise<string> =>
getRouteShareSignature(routeName).then((signature) =>
createQCameraStreamUrl(routeName, signature),
)
14 changes: 5 additions & 9 deletions src/components/RouteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@ 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],
)
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')}`
const subhead = () => `${startTime().format('h:mm A')} to ${endTime().format('h:mm A')}`

return (
<CardHeader
Expand All @@ -34,7 +30,7 @@ const RouteHeader = (props: { route: Route }) => {
}

interface RouteCardProps {
route: Route
route: RouteSegments
}

const RouteCard: VoidComponent<RouteCardProps> = (props) => {
Expand Down
14 changes: 4 additions & 10 deletions src/components/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ 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,
events: TimelineEvent[],
) {
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 (
<For each={events}>
{(event) => {
Expand Down Expand Up @@ -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 (
<div
class="absolute top-0 z-10 h-full"
Expand Down
17 changes: 7 additions & 10 deletions src/pages/dashboard/components/RouteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import type { VoidComponent } from 'solid-js'
import clsx from 'clsx'

import type { Route } from '~/types'
import type { RouteSegments } from '~/types'

import RouteCard from '~/components/RouteCard'
import { fetcher } from '~/api'
Expand All @@ -22,26 +22,23 @@ type RouteListProps = {
dongleId: string
}

const pages: Promise<Route[]>[] = []
const pages: Promise<RouteSegments[]>[] = []

const RouteList: VoidComponent<RouteListProps> = (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<Route[]> => {
const getPage = (page: number): Promise<RouteSegments[]> => {
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<Route[]>(key) : [])
resolve(key ? fetcher<RouteSegments[]>(key) : [])
})
}
return pages[page]
Expand Down
65 changes: 43 additions & 22 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> {
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 {
Expand Down
19 changes: 10 additions & 9 deletions src/utils/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down