Web frontend: perf panel, cover warmup, incremental browse rendering
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,8 @@ import type { Group, Mode, Results } from "./search";
|
||||
|
||||
export interface UiState {
|
||||
search: string;
|
||||
/** `/` was pressed: show the search box before the first letter arrives. */
|
||||
searchOpen: boolean;
|
||||
mode: Mode;
|
||||
/** `null` is the bare root screen (three shelves); otherwise which one is open. */
|
||||
group: Group | null;
|
||||
@@ -41,6 +43,7 @@ export interface UiState {
|
||||
|
||||
export const initialUiState: UiState = {
|
||||
search: "",
|
||||
searchOpen: false,
|
||||
mode: "albums",
|
||||
group: null,
|
||||
category: null,
|
||||
@@ -179,7 +182,9 @@ function moveSelection(state: UiState, results: Results, dx: number, dy: number)
|
||||
* should be one step too, not two, regardless of whether a category was reached that
|
||||
* way or by opening the group first. Always safe to call. */
|
||||
export function browseBackActions(state: UiState): Action[] {
|
||||
if (state.search) return [{ type: "ui", patch: { search: "", selIndex: 0 } }];
|
||||
if (state.search || state.searchOpen) {
|
||||
return [{ type: "ui", patch: { search: "", searchOpen: false, selIndex: 0 } }];
|
||||
}
|
||||
if (state.mode === "tracks") return [{ type: "ui", patch: { mode: "albums", selIndex: 0 } }];
|
||||
return [{ type: "ui", patch: { group: null, category: null, selIndex: 0 } }];
|
||||
}
|
||||
@@ -201,9 +206,28 @@ function escape(state: UiState): Action[] {
|
||||
return browseBackActions(state);
|
||||
}
|
||||
|
||||
/** The true root: nothing chosen yet, rendered as three shelves rather than a list. */
|
||||
/** Whether the search box is on screen: something typed, `/` pressed, or track search. */
|
||||
export function searchBoxShown(state: UiState): boolean {
|
||||
return state.search.length > 0 || state.searchOpen || state.mode === "tracks";
|
||||
}
|
||||
|
||||
/** The true root: nothing chosen yet, rendered as three shelves rather than a list. The
|
||||
* shelves go away the moment the search box appears, before there is a query to filter by. */
|
||||
export function isRootShelf(state: UiState): boolean {
|
||||
return state.group === null && !state.search && state.mode === "albums" && state.category === null;
|
||||
return state.group === null && !searchBoxShown(state) && state.category === null;
|
||||
}
|
||||
|
||||
/** Album search <-> title search, keeping whatever has been typed. `searchOpen` keeps the
|
||||
* box up when the switch lands on albums with nothing typed, where it would otherwise
|
||||
* vanish and bring the shelves back. */
|
||||
export function toggleModeActions(state: UiState): Action[] {
|
||||
return [
|
||||
{ type: "pop", freq: 460 },
|
||||
{
|
||||
type: "ui",
|
||||
patch: { mode: state.mode === "tracks" ? "albums" : "tracks", searchOpen: true, selIndex: 0 },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export interface KeyEvent {
|
||||
@@ -340,7 +364,9 @@ export function handleKey(
|
||||
];
|
||||
}
|
||||
case "/":
|
||||
return [{ type: "ui", patch: { view: "browse" } }];
|
||||
// With the box already up, `/` flips what it searches; otherwise it opens it.
|
||||
if (browsing && searchBoxShown(state)) return toggleModeActions(state);
|
||||
return [{ type: "ui", patch: { view: "browse", searchOpen: true } }];
|
||||
|
||||
case "ArrowRight":
|
||||
if (event.shiftKey) return [{ type: "seek", delta: SEEK_STEP }];
|
||||
|
||||
Reference in New Issue
Block a user