feat(web): order satisfied titles last
The library hid satisfied titles behind a toggle, so a finished show was gone from the view that lists it. Ordering says the same thing without taking anything away.
This commit is contained in:
+5
-4
@@ -51,8 +51,9 @@ export async function fetchLibrary(): Promise<LibraryOutcome> {
|
||||
}
|
||||
|
||||
/**
|
||||
* §4.2: the default list shows `airing` and `incomplete`; everything else
|
||||
* collapses behind one toggle. A satisfied title leaves by itself.
|
||||
* §4.2: `airing` and `incomplete` sort to the top of the list; everything
|
||||
* else is satisfied and sinks below it. A satisfied title steps aside by
|
||||
* itself, and is still there to click.
|
||||
*/
|
||||
export function seriesNeedsAttention(series: LibrarySeries): boolean {
|
||||
return series.status === "airing" || series.status === "incomplete";
|
||||
@@ -76,8 +77,8 @@ export function seriesStatusChipRedundant(series: {
|
||||
}
|
||||
|
||||
/**
|
||||
* The movie analogue of §4.2's default set: wanted and not yet on disk.
|
||||
* Available and unwanted titles are satisfied and collapse with the rest.
|
||||
* The movie analogue of §4.2's ordering: wanted and not yet on disk sorts
|
||||
* first. Available and unwanted titles are satisfied and sink with the rest.
|
||||
*/
|
||||
export function movieNeedsAttention(movie: LibraryMovie): boolean {
|
||||
return movie.wanted && movie.state !== "available";
|
||||
|
||||
+32
-29
@@ -3651,6 +3651,14 @@ function loadLibraryShape(): LibraryShape {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stable partition: the items that still want something keep their order and
|
||||
* come first, the satisfied ones keep theirs and follow.
|
||||
*/
|
||||
function attentionFirst<T>(items: T[], wants: (item: T) => boolean): T[] {
|
||||
return [...items.filter(wants), ...items.filter((item) => !wants(item))];
|
||||
}
|
||||
|
||||
function libraryMain(
|
||||
movieDetail: MovieView,
|
||||
seriesDetail: SeriesView,
|
||||
@@ -3659,7 +3667,6 @@ function libraryMain(
|
||||
const view = must<HTMLElement>("#library");
|
||||
const deck = must<HTMLElement>("#deck");
|
||||
const summary = must<HTMLElement>("#library-summary");
|
||||
const toggle = must<HTMLButtonElement>("#library-toggle");
|
||||
const status = must<HTMLElement>("#library-status");
|
||||
const gridBtn = must<HTMLButtonElement>("#library-view-grid");
|
||||
const listBtn = must<HTMLButtonElement>("#library-view-list");
|
||||
@@ -3677,8 +3684,6 @@ function libraryMain(
|
||||
},
|
||||
};
|
||||
|
||||
// §4.2: ONE toggle for everything the default view leaves out
|
||||
let showAll = false;
|
||||
// §9.6 library view: the poster grid is default, per-person, localStorage
|
||||
let shape = loadLibraryShape();
|
||||
let data: { series: LibrarySeries[]; movies: LibraryMovie[] } | null = null;
|
||||
@@ -3721,7 +3726,6 @@ function libraryMain(
|
||||
group.rows.replaceChildren();
|
||||
}
|
||||
summary.textContent = "";
|
||||
toggle.hidden = true;
|
||||
}
|
||||
|
||||
function renderGroup<T>(group: LibraryGroup, items: T[], build: (item: T) => HTMLLIElement) {
|
||||
@@ -3777,17 +3781,13 @@ function libraryMain(
|
||||
return;
|
||||
}
|
||||
const total = library.series.length + library.movies.length;
|
||||
const satisfied =
|
||||
library.series.filter((series) => !seriesNeedsAttention(series)).length +
|
||||
library.movies.filter((movie) => !movieNeedsAttention(movie)).length;
|
||||
|
||||
summary.textContent = total === 0 ? "" : `${total} ${total === 1 ? "title" : "titles"}`;
|
||||
toggle.hidden = satisfied === 0;
|
||||
toggle.textContent = `${showAll ? "hide" : "show"} ${satisfied} satisfied`;
|
||||
toggle.setAttribute("aria-expanded", String(showAll));
|
||||
|
||||
const seriesShown = showAll ? library.series : library.series.filter(seriesNeedsAttention);
|
||||
const moviesShown = showAll ? library.movies : library.movies.filter(movieNeedsAttention);
|
||||
// §4.2: nothing is hidden. Satisfied titles sort last so what still
|
||||
// wants something is what the eye lands on first.
|
||||
const seriesShown = attentionFirst(library.series, seriesNeedsAttention);
|
||||
const moviesShown = attentionFirst(library.movies, movieNeedsAttention);
|
||||
|
||||
const openSeries = (id: number, origin: HTMLElement) => {
|
||||
navigate({ kind: "series", seriesId: id });
|
||||
@@ -3801,16 +3801,26 @@ function libraryMain(
|
||||
if (shape === "grid") {
|
||||
groups.series.section.hidden = true;
|
||||
groups.movies.section.hidden = true;
|
||||
renderLibraryRows([
|
||||
...seriesShown.map((series) => ({
|
||||
root_id: series.root_id,
|
||||
item: seriesCard(series, openSeries),
|
||||
})),
|
||||
...moviesShown.map((movie) => ({
|
||||
root_id: movie.root_id,
|
||||
item: movieCard(movie, openMovie),
|
||||
})),
|
||||
]);
|
||||
// series and movies share a root section, so the two lists interleave
|
||||
// before the cards are built — otherwise a satisfied series would sit
|
||||
// above a movie that still wants a file.
|
||||
renderLibraryRows(
|
||||
attentionFirst(
|
||||
[
|
||||
...seriesShown.map((series) => ({
|
||||
root_id: series.root_id,
|
||||
wants: seriesNeedsAttention(series),
|
||||
build: () => seriesCard(series, openSeries),
|
||||
})),
|
||||
...moviesShown.map((movie) => ({
|
||||
root_id: movie.root_id,
|
||||
wants: movieNeedsAttention(movie),
|
||||
build: () => movieCard(movie, openMovie),
|
||||
})),
|
||||
],
|
||||
(entry) => entry.wants,
|
||||
).map((entry) => ({ root_id: entry.root_id, item: entry.build() })),
|
||||
);
|
||||
} else {
|
||||
libraryRows.replaceChildren();
|
||||
libraryRows.hidden = true;
|
||||
@@ -3820,8 +3830,6 @@ function libraryMain(
|
||||
|
||||
if (total === 0) {
|
||||
setStatus("library is empty — the search box above adds titles");
|
||||
} else if (seriesShown.length + moviesShown.length === 0) {
|
||||
setStatus("nothing needs attention");
|
||||
} else {
|
||||
setStatus(null);
|
||||
}
|
||||
@@ -3862,11 +3870,6 @@ function libraryMain(
|
||||
sequence += 1;
|
||||
}
|
||||
|
||||
toggle.addEventListener("click", () => {
|
||||
showAll = !showAll;
|
||||
render();
|
||||
});
|
||||
|
||||
function repaintDownloads() {
|
||||
if (!view.hidden) {
|
||||
render();
|
||||
|
||||
Reference in New Issue
Block a user