Fix search and sort

This commit is contained in:
dirwin31
2026-08-27 17:36:07 -07:00
parent 3c852bdbb0
commit a0aa48f82a
3 changed files with 45 additions and 11 deletions
@@ -825,11 +825,11 @@ export function RouteRecordings() {
</div>
<label class="dashcam-sort">
<span>Sort</span>
<select @change="${event => { state.sortOrder = event.target.value }}">
<option value="newest" selected="${() => state.sortOrder === "newest" || false}">Newest first</option>
<option value="oldest" selected="${() => state.sortOrder === "oldest" || false}">Oldest first</option>
<option value="longest" selected="${() => state.sortOrder === "longest" || false}">Longest duration</option>
<option value="shortest" selected="${() => state.sortOrder === "shortest" || false}">Shortest duration</option>
<select value="${() => state.sortOrder}" @change="${event => { state.sortOrder = event.target.value }}">
<option value="newest">Newest first</option>
<option value="oldest">Oldest first</option>
<option value="longest">Longest duration</option>
<option value="shortest">Shortest duration</option>
</select>
</label>
<div class="dashcam-view-toggle" aria-label="Layout view mode">
@@ -862,7 +862,7 @@ export function RouteRecordings() {
<h2>${group.label}</h2>
<span class="dashcam-date-group-count">${group.routes.length} ${group.routes.length === 1 ? "drive" : "drives"}</span>
</div>
${() => state.viewMode === "grid" ? html`<div class="screen-recordings-grid dashcam-routes-grid">
${state.viewMode === "grid" ? html`<div class="screen-recordings-grid dashcam-routes-grid">
${group.routes.map(route => html`
<article class="${() => `recording-card dashcam-route-card ${route.is_preserved ? "is-preserved" : ""}`}" @click="${() => { state.selectedRoute = route }}">
<div class="dashcam-card-top-bar">
@@ -901,7 +901,7 @@ export function RouteRecordings() {
</button>
</div>
</div>
</article>`)}
</article>`.key(route.name))}
</div>` : html`<div class="dashcam-routes-list">
${group.routes.map(route => html`
<article class="${() => `dashcam-route-row ${route.is_preserved ? "is-preserved" : ""}`}" @click="${() => { state.selectedRoute = route }}">
@@ -940,9 +940,9 @@ export function RouteRecordings() {
<i class="bi bi-trash"></i>
</button>
</div>
</article>`)}
</article>`.key(route.name))}
</div>`}
</section>`)}
</section>`.key(group.key))}
</div>
${view.truncated ? html`<p class="screen-recordings-message">Showing the first ${MAX_RENDERED_ROUTES} of ${view.matching.length} matching routes.</p>` : ""}`
}}
@@ -40,6 +40,11 @@ export function normalizeRoute(route, locale) {
`${routeDate.getMonth() + 1}/${routeDate.getDate()}/${routeDate.getFullYear()}`,
`${routeDate.getFullYear()}-${routeDate.getMonth() + 1}-${routeDate.getDate()}`,
] : []
const timeAliases = routeDate ? [
new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "2-digit" }).format(routeDate),
new Intl.DateTimeFormat("en-US", { hour: "numeric", minute: "2-digit", hour12: true }).format(routeDate),
`${String(routeDate.getHours()).padStart(2, "0")}:${String(routeDate.getMinutes()).padStart(2, "0")}`,
] : []
return {
...route,
@@ -54,6 +59,7 @@ export function normalizeRoute(route, locale) {
normalizeRouteSearchText(route?.name),
normalizeRouteSearchText(isCustomName ? displayName : ""),
...dateAliases.map(normalizeRouteSearchText),
...timeAliases.map(normalizeRouteSearchText),
].filter(Boolean),
}
}
@@ -120,7 +126,8 @@ export function sortRoutes(routes, sortOrder = "newest") {
}
export function routeMatchesSearch(route, searchQuery) {
const queryTokens = normalizeRouteSearchText(searchQuery).split(" ").filter(Boolean)
const normalizedQuery = normalizeRouteSearchText(searchQuery)
const queryTokens = normalizedQuery.split(" ").filter(Boolean)
if (!queryTokens.length) return true
const searchValues = Array.isArray(route?._searchValues) ? route._searchValues : [
@@ -130,8 +137,9 @@ export function routeMatchesSearch(route, searchQuery) {
route?.displayDate,
].map(normalizeRouteSearchText).filter(Boolean)
return searchValues.some(value => {
if (value.includes(normalizedQuery)) return true
const searchTokens = value.split(" ").filter(Boolean)
return queryTokens.every(queryToken => searchTokens.some(searchToken => searchToken.includes(queryToken)))
return queryTokens.every(queryToken => searchTokens.some(searchToken => searchToken.startsWith(queryToken)))
})
}
@@ -79,6 +79,14 @@ def test_route_titles_and_custom_name_badges_are_reactive():
assert source.count('${() => route.isCustomName ? html`') >= 4
def test_sort_order_select_and_route_items_are_keyed_and_reactive():
source = COMPONENT_PATH.read_text(encoding="utf-8")
assert '<select value="${() => state.sortOrder}" @change="${event => { state.sortOrder = event.target.value }}">' in source
assert ".key(group.key)" in source
assert ".key(route.name)" in source
def test_player_shell_matches_low_quality_video_aspect_ratio():
source = COMPONENT_CSS_PATH.read_text(encoding="utf-8")
@@ -163,6 +171,24 @@ def test_route_search_input_updates_on_every_keystroke():
assert '@input="${event => { state.searchQuery = event.target.value }}"' in source
def test_search_indexes_the_displayed_time_for_every_route():
result = evaluate('''
const routes = [
route("0000006a--9f0a7bdf9c", "2026-08-27T12:15:00Z"),
route("0000006b--9f0a7bdf9d", "2026-08-27T12:50:00Z"),
route("0000006c--9f0a7bdf9e", "2026-08-27T13:05:00Z"),
]
return ["12", "12:5", "12:15"]
.map(searchQuery => buildRouteView(routes, { searchQuery }).matching.map(item => item.name))
''')
assert result == [
["0000006b--9f0a7bdf9d", "0000006a--9f0a7bdf9c"],
["0000006b--9f0a7bdf9d"],
["0000006a--9f0a7bdf9c"],
]
def test_filters_preserved_routes_before_applying_the_render_limit():
view = evaluate('''
const routes = Array.from({ length: MAX_RENDERED_ROUTES + 25 }, (_, index) => route(