fix(playlists): name the view state in one sentence

Sorting a playlist whose stored order already matches the sort produced a
warning, no Save button and no visible change — which reads as a malfunction,
not as "there is nothing to save". And when the order DID change, three separate
texts piled up in the toolbar (a reset link, a save hint, a "reordering is off"
notice), none of which answered the question actually being asked: did I just
change my playlist?

One line now names which of the three situations you are in — the view matches
the saved order, the view differs and can be saved, or a filter is on and saving
is impossible — and says in each case that the saved order is untouched. The
disabled grip still explains itself on hover and focus, so that notice no longer
needs to stand in the toolbar.
This commit is contained in:
2026-07-21 00:40:49 +02:00
parent cdec80547e
commit f87d58d8d7
3 changed files with 36 additions and 11 deletions
+24 -7
View File
@@ -492,6 +492,23 @@ export default function Playlists() {
// every render re-subscribed those listeners and re-ran focusModal() under the open player.
const closePlayer = useCallback(() => setPlayingId(null), []);
// Name the current view in the user's terms: "Title ↑", "grouped by channel", or both.
const sortLabel = viewSort && !isStoredSort(viewSort)
? `${columns.find((c) => c.key === viewSort.key)?.header ?? viewSort.key} ${
viewSort.dir === "asc" ? "↑" : "↓"
}`
: null;
const viewName = groupBy
? sortLabel
? t("playlists.viewGroupedAnd", { sort: sortLabel })
: t("playlists.viewGrouped")
: (sortLabel ?? "");
const viewStatus = filterActive
? t("playlists.viewFiltered", { shown: shown.length, total: items.length })
: canSaveOrder
? t("playlists.viewDiffers", { view: viewName })
: t("playlists.viewMatches", { view: viewName });
// Order matters: a built-in list is never reorderable, whatever the view says.
const dragHint = !canEdit
? t("playlists.dragBlockedBuiltin")
@@ -670,19 +687,19 @@ export default function Playlists() {
{canSaveOrder && (
<button
onClick={saveOrder}
aria-describedby="pl-save-hint"
className="inline-flex items-center gap-1.5 text-xs font-semibold px-2.5 py-1 rounded-lg border border-accent text-accent hover:bg-accent/10 transition"
>
<Check className="w-3.5 h-3.5" /> {t("playlists.saveOrder")}
</button>
)}
{canSaveOrder && (
<span id="pl-save-hint" className="text-[11px] text-muted/80">
{t("playlists.saveOrderHint")}
</span>
)}
{/* ONE sentence that names the state. Three loose texts used to pile up here (reset
link + save hint + "reordering is off"), and none of them answered the question a
user actually has — "did I just change my playlist?". Worse, sorting a list whose
stored order ALREADY matches the sort produced a warning, no Save button and no
visible change, which reads as a malfunction. Now the line says which of the three
situations you are in; the disabled grip still explains itself on hover/focus. */}
{viewActive && (
<span className="text-[11px] text-muted/80">{dragHint}</span>
<span className="text-[11px] text-muted/80">{viewStatus}</span>
)}
{canEdit && (
<div className="ml-auto">
+6 -2
View File
@@ -65,7 +65,6 @@
"dragBlockedFilter": "Reordering by hand is off while a filter is applied.",
"clearView": "Reset view",
"saveOrder": "Save this order",
"saveOrderHint": "Store the order you see as the playlist's own order (undoable).",
"filteredCount": "{{count}} shown",
"noneMatchFilter": "No videos match the filter.",
"loadFailed": "Couldn't load this playlist.",
@@ -79,5 +78,10 @@
"dndPickedUp": "Picked up “{{title}}”, position {{pos}} of {{total}}.",
"dndMovedOver": "“{{title}}” moved to position {{pos}} of {{total}}.",
"dndDropped": "“{{title}}” dropped at position {{pos}} of {{total}}.",
"dndCancelled": "Move cancelled; “{{title}}” is back at position {{pos}}."
"dndCancelled": "Move cancelled; “{{title}}” is back at position {{pos}}.",
"viewGrouped": "grouped by channel",
"viewGroupedAnd": "grouped by channel, {{sort}}",
"viewDiffers": "Temporary view ({{view}}) — the playlist's saved order is unchanged. Save it to keep this order.",
"viewMatches": "This view ({{view}}) already matches the playlist's saved order, so there is nothing to save.",
"viewFiltered": "Filtered view — {{shown}} of {{total}} shown. The order can't be saved while a filter is on."
}
+6 -2
View File
@@ -65,7 +65,6 @@
"dragBlockedFilter": "A kézi átrendezés ki van kapcsolva, amíg szűrő van érvényben.",
"clearView": "Nézet visszaállítása",
"saveOrder": "Sorrend mentése",
"saveOrderHint": "A látott sorrend legyen a lista saját sorrendje (visszavonható).",
"filteredCount": "{{count}} látszik",
"noneMatchFilter": "Egy videó sem felel meg a szűrőnek.",
"loadFailed": "Nem sikerült betölteni ezt a listát.",
@@ -79,5 +78,10 @@
"dndPickedUp": "„{{title}}” felvéve, {{pos}}. a(z) {{total}} közül.",
"dndMovedOver": "„{{title}}” a(z) {{pos}}. helyre került a(z) {{total}} közül.",
"dndDropped": "„{{title}}” letéve a(z) {{pos}}. helyre a(z) {{total}} közül.",
"dndCancelled": "Mozgatás megszakítva; „{{title}}” visszakerült a(z) {{pos}}. helyre."
"dndCancelled": "Mozgatás megszakítva; „{{title}}” visszakerült a(z) {{pos}}. helyre.",
"viewGrouped": "csatorna szerint csoportosítva",
"viewGroupedAnd": "csatorna szerint csoportosítva, {{sort}}",
"viewDiffers": "Ideiglenes nézet ({{view}}) — a lista mentett sorrendje változatlan. Mentsd, ha ez maradjon.",
"viewMatches": "Ez a nézet ({{view}}) megegyezik a lista mentett sorrendjével, így nincs mit menteni.",
"viewFiltered": "Szűrt nézet — {{total}} elemből {{shown}} látszik. Szűrés közben a sorrend nem menthető."
}