feat(design): the channel menu's Tags and Priority submenus (C1, E28 S3e3b)
Tags A-Z as in-place checks (the menu stays open for the next); Priority as the fixed steps 10 5 3 2 1 0, an off-scale current value ticked in its place. A pick re-sorts the list at once, moving only that row (placeByPriority, the server's order), and the table / card pager and the focus follow it (DataTable and useCardPager take a reveal seq, listNav.focusNavItem lands on the row's own "…", a virtualized card revealed first). ActionMenu rows take check: many | one. The menu reads the row fresh from the cache and keys its target by id.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
// The channel manager's list order, and the priority steps its menu offers (E28 S3e3b).
|
||||
|
||||
/** The steps a channel's menu offers (owner, 2026-09-26): fixed levels, so bubbling a channel up
|
||||
* the list is one pick, not a run of +1 presses, and not straight to the top either. The row's
|
||||
* stepper still sets any value, negatives included. */
|
||||
const PRIORITY_STEPS: readonly number[] = [10, 5, 3, 2, 1, 0];
|
||||
|
||||
/** The steps to offer for a channel at `current`: the fixed ones, plus `current` itself in its
|
||||
* place when it is off the scale (a 7, a -2) — the menu always shows, ticked, where it is now. */
|
||||
export function priorityChoices(current: number): number[] {
|
||||
return PRIORITY_STEPS.includes(current)
|
||||
? [...PRIORITY_STEPS]
|
||||
: [...PRIORITY_STEPS, current].sort((a, b) => b - a);
|
||||
}
|
||||
|
||||
interface Ordered {
|
||||
id: string;
|
||||
priority: number;
|
||||
title?: string | null;
|
||||
}
|
||||
|
||||
/** The server's order (`routes/channels.py`: priority desc, then the title case-folded, a missing
|
||||
* title last). */
|
||||
function before(a: Ordered, b: Ordered): boolean {
|
||||
if (a.priority !== b.priority) return a.priority > b.priority;
|
||||
if (a.title == null) return false;
|
||||
if (b.title == null) return true;
|
||||
return a.title.toLowerCase() < b.title.toLowerCase();
|
||||
}
|
||||
|
||||
/** `rows` with the channel `id` taken out and put back where its priority now places it, every
|
||||
* other row left exactly where it was — the server's order, without a refetch that would reorder
|
||||
* anything the reader did not touch. */
|
||||
export function placeByPriority<T extends Ordered>(rows: readonly T[], id: string): T[] {
|
||||
const moved = rows.find((r) => r.id === id);
|
||||
if (!moved) return [...rows];
|
||||
const rest = rows.filter((r) => r !== moved);
|
||||
const at = rest.findIndex((r) => before(moved, r));
|
||||
rest.splice(at < 0 ? rest.length : at, 0, moved);
|
||||
return rest;
|
||||
}
|
||||
Reference in New Issue
Block a user