mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-01 05:33:49 +08:00
Fix Search, no jumpy video
This commit is contained in:
@@ -1254,7 +1254,8 @@
|
||||
}
|
||||
|
||||
.dashcam-video-shell {
|
||||
aspect-ratio: 16 / 9;
|
||||
/* Match qcamera.ts so the visible frame does not widen during the full-quality swap. */
|
||||
aspect-ratio: 526 / 330;
|
||||
background: #07080b;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
@@ -6,6 +6,16 @@ function validDate(value) {
|
||||
return Number.isNaN(date.getTime()) ? null : date
|
||||
}
|
||||
|
||||
export function normalizeRouteSearchText(value) {
|
||||
return String(value || "")
|
||||
.normalize("NFKD")
|
||||
.replace(/\p{M}/gu, "")
|
||||
.toLocaleLowerCase()
|
||||
.replace(/(\d+)(?:st|nd|rd|th)\b/g, "$1")
|
||||
.replace(/[^\p{L}\p{N}]+/gu, " ")
|
||||
.trim()
|
||||
}
|
||||
|
||||
export function formatRouteDate(value, locale) {
|
||||
const date = validDate(value)
|
||||
if (!date) return "Unknown date"
|
||||
@@ -19,8 +29,17 @@ export function normalizeRoute(route, locale) {
|
||||
const timestamp = route?.timestamp == null ? null : String(route.timestamp)
|
||||
const startedAtDate = validDate(route?.startedAt)
|
||||
const timestampDate = validDate(timestamp)
|
||||
const displayDate = formatRouteDate(startedAtDate || timestampDate, locale)
|
||||
const routeDate = startedAtDate || timestampDate
|
||||
const displayDate = formatRouteDate(routeDate, locale)
|
||||
const isCustomName = Boolean(route?.isCustomName) || Boolean(timestamp && !timestampDate)
|
||||
const displayName = isCustomName ? timestamp : displayDate
|
||||
const dateAliases = routeDate ? [
|
||||
new Intl.DateTimeFormat(locale, { dateStyle: "long" }).format(routeDate),
|
||||
new Intl.DateTimeFormat("en-US", { month: "long", day: "numeric", year: "numeric" }).format(routeDate),
|
||||
new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric" }).format(routeDate),
|
||||
`${routeDate.getMonth() + 1}/${routeDate.getDate()}/${routeDate.getFullYear()}`,
|
||||
`${routeDate.getFullYear()}-${routeDate.getMonth() + 1}-${routeDate.getDate()}`,
|
||||
] : []
|
||||
|
||||
return {
|
||||
...route,
|
||||
@@ -29,8 +48,13 @@ export function normalizeRoute(route, locale) {
|
||||
startedAt: route?.startedAt || null,
|
||||
isCustomName,
|
||||
displayDate,
|
||||
displayName: isCustomName ? timestamp : displayDate,
|
||||
displayName,
|
||||
_startedAtMs: startedAtDate?.getTime() ?? timestampDate?.getTime() ?? null,
|
||||
_searchValues: [
|
||||
normalizeRouteSearchText(route?.name),
|
||||
normalizeRouteSearchText(isCustomName ? displayName : ""),
|
||||
...dateAliases.map(normalizeRouteSearchText),
|
||||
].filter(Boolean),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,11 +120,19 @@ export function sortRoutes(routes, sortOrder = "newest") {
|
||||
}
|
||||
|
||||
export function routeMatchesSearch(route, searchQuery) {
|
||||
const query = String(searchQuery || "").trim().toLocaleLowerCase()
|
||||
if (!query) return true
|
||||
return [route?.name, route?.timestamp, route?.displayName, route?.displayDate]
|
||||
.filter(Boolean)
|
||||
.some(value => String(value).toLocaleLowerCase().includes(query))
|
||||
const queryTokens = normalizeRouteSearchText(searchQuery).split(" ").filter(Boolean)
|
||||
if (!queryTokens.length) return true
|
||||
|
||||
const searchValues = Array.isArray(route?._searchValues) ? route._searchValues : [
|
||||
route?.name,
|
||||
route?.timestamp,
|
||||
route?.displayName,
|
||||
route?.displayDate,
|
||||
].map(normalizeRouteSearchText).filter(Boolean)
|
||||
return searchValues.some(value => {
|
||||
const searchTokens = value.split(" ").filter(Boolean)
|
||||
return queryTokens.every(queryToken => searchTokens.some(searchToken => searchToken.includes(queryToken)))
|
||||
})
|
||||
}
|
||||
|
||||
export function buildRouteView(routes, options = {}) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import pytest
|
||||
|
||||
HELPERS_PATH = Path(__file__).resolve().parent.parent / "assets" / "components" / "recordings" / "dashcam_routes_helpers.js"
|
||||
COMPONENT_PATH = HELPERS_PATH.with_name("dashcam_routes.js")
|
||||
COMPONENT_CSS_PATH = HELPERS_PATH.with_name("dashcam_routes.css")
|
||||
|
||||
# node infers ESM from `export` syntax in a bare .js file from 22.7 on, so the helpers
|
||||
# need no package.json and stay a normal asset next to the component that imports them.
|
||||
@@ -78,6 +79,12 @@ def test_route_titles_and_custom_name_badges_are_reactive():
|
||||
assert source.count('${() => route.isCustomName ? html`') >= 4
|
||||
|
||||
|
||||
def test_player_shell_matches_low_quality_video_aspect_ratio():
|
||||
source = COMPONENT_CSS_PATH.read_text(encoding="utf-8")
|
||||
|
||||
assert "aspect-ratio: 526 / 330;" in source
|
||||
|
||||
|
||||
def test_groups_routes_into_today_yesterday_dates_and_unknown():
|
||||
groups = evaluate('''
|
||||
const routes = [
|
||||
@@ -129,6 +136,33 @@ def test_searches_custom_names_displayed_dates_and_route_ids():
|
||||
assert matches == [1, 1, 1, 0]
|
||||
|
||||
|
||||
def test_search_matches_partial_title_tokens_and_friendly_dates_as_the_user_types():
|
||||
result = evaluate('''
|
||||
const routes = [
|
||||
route("0000006a--9f0a7bdf9c", "2026-08-27T08:00:00Z", { timestamp: "Test_31", isCustomName: true }),
|
||||
route("0000006b--9f0a7bdf9d", "2026-08-28T08:00:00Z", { timestamp: "Morning drive", isCustomName: true }),
|
||||
route("0000006c--9f0a7bdf9e", "2026-09-27T08:00:00Z", { timestamp: "Test_4", isCustomName: true }),
|
||||
]
|
||||
return ["test", "test 3", "aug", "aug 2", "aug 27th", "8/27"]
|
||||
.map(searchQuery => buildRouteView(routes, { searchQuery }).matching.map(item => item.name))
|
||||
''')
|
||||
|
||||
assert result == [
|
||||
["0000006c--9f0a7bdf9e", "0000006a--9f0a7bdf9c"],
|
||||
["0000006a--9f0a7bdf9c"],
|
||||
["0000006b--9f0a7bdf9d", "0000006a--9f0a7bdf9c"],
|
||||
["0000006b--9f0a7bdf9d", "0000006a--9f0a7bdf9c"],
|
||||
["0000006a--9f0a7bdf9c"],
|
||||
["0000006a--9f0a7bdf9c"],
|
||||
]
|
||||
|
||||
|
||||
def test_route_search_input_updates_on_every_keystroke():
|
||||
source = COMPONENT_PATH.read_text(encoding="utf-8")
|
||||
|
||||
assert '@input="${event => { state.searchQuery = event.target.value }}"' in source
|
||||
|
||||
|
||||
def test_filters_preserved_routes_before_applying_the_render_limit():
|
||||
view = evaluate('''
|
||||
const routes = Array.from({ length: MAX_RENDERED_ROUTES + 25 }, (_, index) => route(
|
||||
|
||||
Reference in New Issue
Block a user