(t: TFu
};
}
-function SyncBadge({ ok, label, hint }: { ok: boolean; label: string; hint?: string }) {
- return (
-
-
- {ok && }
- {label}
-
-
- );
+// A channel is "fully synced" when its recent uploads AND full back-catalogue are both in — the
+// predicate the retired Sync column used, now surfaced as a ✓ charm on the Stored count and driven
+// by the top sync-filter chips.
+const isFullySynced = (c: ManagedChannel) => c.recent_synced && c.backfill_done;
+
+/** Sync-status pre-filter for the subscribed table. The Sync COLUMN is gone (R14) — its status folded
+ * into a charm on Stored — so the top chips' `filters.sync` value is applied here instead of via a
+ * column predicate. Same vocab as the old column filter: "fully" | "needsFull" | "" (all). */
+export function applySyncFilter(rows: ManagedChannel[], value: unknown): ManagedChannel[] {
+ if (value === "fully") return rows.filter(isFullySynced);
+ if (value === "needsFull") return rows.filter((c) => !c.backfill_done);
+ return rows;
}
-// Status only — the backfill *action* lives in the Actions column. When both recent and
-// full history are in, collapse to a single "fully synced" chip; otherwise show what's
-// present plus the missing/queued state.
-function SyncCell({ c }: { c: ManagedChannel }) {
+// The Stored count, with a "fully synced" ✓ charm to the left of the number when recent uploads and
+// full history are both in — folding the retired Sync column's at-a-glance status into the value it
+// describes. Full detail stays in the hover tooltip.
+function StoredCell({ c }: { c: ManagedChannel }) {
const { t } = useTranslation();
- if (c.recent_synced && c.backfill_done) {
- return (
-
- );
- }
return (
-
-
- {c.backfill_done ? (
-
- ) : c.deep_requested ? (
-
- ) : c.deep_in_queue ? (
-
- ) : (
-
+
+ {isFullySynced(c) && (
+
+
+
)}
-
+ {c.stored_videos.toLocaleString()}
+
);
}
@@ -252,11 +221,11 @@ export function buildChannelColumns(deps: ChannelColumnDeps): Column (c.title ?? c.id).toLowerCase(),
filter: { kind: "text", get: (c) => `${c.title ?? ""} ${c.handle ?? ""}` },
cardPrimary: true,
- // Cap the width so a long-titled channel can't stretch the column (table-layout:auto ignores a
- // cell max-width, but a max-w element INSIDE the cell bounds it — same trick AboutCell uses).
- // Without this a single long name blows the column ~289→429px and overflows the centered table.
+ // A FLUID column (R14): no width cap. Under the manager table's fixed layout this cell takes a
+ // share of the leftover space and ChannelLink truncates the name to fit (full name on hover via
+ // title), so a long name can't push the table wider than its container at any zoom.
render: (c) => (
-
+
,
},
{
@@ -278,17 +248,18 @@ export function buildChannelColumns(deps: ChannelColumnDeps): Column c.stored_videos,
- render: (c) => (
- {c.stored_videos.toLocaleString()}
- ),
+ // The stored count carries the "fully synced" ✓ charm (R14 folded the retired Sync column in).
+ render: (c) => ,
},
- subsColumn(t),
+ { ...subsColumn(t), width: "6rem" },
{
key: "lastUpload",
header: t("channels.cols.lastUpload"),
nowrap: true,
+ width: "6.5rem",
sortable: true,
sortValue: (c) => (c.last_video_at ? new Date(c.last_video_at).getTime() : 0),
render: (c) => {relativeTime(c.last_video_at) || "—"},
@@ -298,6 +269,7 @@ export function buildChannelColumns(deps: ChannelColumnDeps): Column c.total_duration_seconds,
render: (c) => (
@@ -306,38 +278,6 @@ export function buildChannelColumns(deps: ChannelColumnDeps): Column
),
},
- {
- key: "types",
- header: t("channels.cols.types"),
- nowrap: true,
- render: (c) => (
-
-
- {c.count_normal} / {c.count_short} / {c.count_live}
-
-
- ),
- },
- {
- key: "sync",
- header: t("channels.cols.sync"),
- nowrap: true,
- cardLabel: false,
- filter: {
- kind: "select",
- options: [
- { value: "fully", label: t("channels.filters.fullySynced") },
- { value: "needsFull", label: t("channels.filters.needsFull") },
- ],
- test: (c, v) =>
- v === "fully"
- ? c.recent_synced && c.backfill_done
- : v === "needsFull"
- ? !c.backfill_done
- : true,
- },
- render: (c) => ,
- },
{
key: "tags",
header: t("channels.cols.tags"),
diff --git a/frontend/src/i18n/locales/en/channel.json b/frontend/src/i18n/locales/en/channel.json
index d7d7ec28..b6bab16a 100644
--- a/frontend/src/i18n/locales/en/channel.json
+++ b/frontend/src/i18n/locales/en/channel.json
@@ -12,6 +12,7 @@
"videoCount_other": "{{count}} videos",
"videoCountStored": "{{stored}} / {{total}} videos",
"totalViews": "{{formatted}} views",
+ "videoTypes": "{{normal}} normal / {{short}} shorts / {{live}} live",
"joined": "Joined {{date}}",
"loadingVideos": "Loading this channel's videos…",
"tabVideos": "Videos",
diff --git a/frontend/src/i18n/locales/en/channels.json b/frontend/src/i18n/locales/en/channels.json
index 57cdcfda..9eb7826e 100644
--- a/frontend/src/i18n/locales/en/channels.json
+++ b/frontend/src/i18n/locales/en/channels.json
@@ -68,9 +68,6 @@
"subs": "Subscribers",
"lastUpload": "Last upload",
"length": "Length",
- "types": "N/S/L",
- "typesHint": "Videos by type: Normal / Shorts / Live",
- "sync": "Sync",
"tags": "Tags",
"actions": "Actions"
},
@@ -97,20 +94,13 @@
"raisePriority": "Raise priority",
"lowerPriority": "Lower priority",
"editPriority": "Click to type a priority (negatives allowed)",
- "recent": "recent",
- "recentSyncedHint": "Recent uploads synced.",
- "recentNotSyncedHint": "Recent uploads not fetched yet.",
- "full": "full",
"fullHint": "Full history fetched.",
- "fullNotFetchedHint": "Full back-catalog not fetched yet — use the backfill action in the Actions column.",
"fullySynced": "fully synced",
"fullySyncedHint": "Recent uploads and the full back-catalog are both in.",
"backfillThis": "Backfill this channel",
"backfillThisHint": "Fetch this channel's full back-catalog (older videos + complete search). Click again to cancel the request.",
"resetBackfill": "Reset & re-fetch this channel",
"resetHint": "Admin: re-fetch this channel from scratch (recent + full back-catalog), regardless of its current sync state.",
- "fullHistoryQueued": "queued",
- "fullHistoryComing": "coming",
"queuedRequestedHint": "Full history requested — this channel's whole back-catalog will backfill as the shared quota allows. Click to cancel your request.",
"queuedByOtherHint": "Another subscriber already requested this channel's full history, so its whole back-catalog is on its way to everyone — nothing to do here.",
"hiddenHint": "Hidden — this channel's videos are kept out of your feed. Click to show them again.",
diff --git a/frontend/src/i18n/locales/hu/channel.json b/frontend/src/i18n/locales/hu/channel.json
index 77463ac2..ddaee097 100644
--- a/frontend/src/i18n/locales/hu/channel.json
+++ b/frontend/src/i18n/locales/hu/channel.json
@@ -12,6 +12,7 @@
"videoCount_other": "{{count}} videó",
"videoCountStored": "{{stored}} / {{total}} videó",
"totalViews": "{{formatted}} megtekintés",
+ "videoTypes": "{{normal}} normál / {{short}} short / {{live}} élő",
"joined": "Csatlakozott: {{date}}",
"loadingVideos": "A csatorna videóinak betöltése…",
"tabVideos": "Videók",
diff --git a/frontend/src/i18n/locales/hu/channels.json b/frontend/src/i18n/locales/hu/channels.json
index 29df7265..6b392cfc 100644
--- a/frontend/src/i18n/locales/hu/channels.json
+++ b/frontend/src/i18n/locales/hu/channels.json
@@ -68,9 +68,6 @@
"subs": "Feliratkozók",
"lastUpload": "Utolsó feltöltés",
"length": "Hossz",
- "types": "N/S/L",
- "typesHint": "Videók típusonként: Normál / Short / Live",
- "sync": "Szinkron",
"tags": "Címkék",
"actions": "Műveletek"
},
@@ -97,20 +94,13 @@
"raisePriority": "Prioritás növelése",
"lowerPriority": "Prioritás csökkentése",
"editPriority": "Kattints a prioritás beírásához (negatív is lehet)",
- "recent": "legutóbbi",
- "recentSyncedHint": "Legutóbbi feltöltések szinkronizálva.",
- "recentNotSyncedHint": "A legutóbbi feltöltések még nincsenek letöltve.",
- "full": "teljes",
"fullHint": "Teljes előzmény letöltve.",
- "fullNotFetchedHint": "A teljes archívum még nincs letöltve — használd a Műveletek oszlop backfill gombját.",
"fullySynced": "teljesen szinkronban",
"fullySyncedHint": "A legutóbbi feltöltések és a teljes archívum is megvan.",
"backfillThis": "Csatorna teljes letöltése",
"backfillThisHint": "A csatorna teljes archívumának letöltése (régebbi videók + teljes keresés). Újra kattintva visszavonod a kérést.",
"resetBackfill": "Csatorna resetelése és újraletöltése",
"resetHint": "Admin: a csatorna teljes újraletöltése a nulláról (legutóbbi + teljes archívum), a jelenlegi szinkron-állapottól függetlenül.",
- "fullHistoryQueued": "sorban",
- "fullHistoryComing": "úton",
"queuedRequestedHint": "Teljes előzmény kérve — a csatorna teljes archívuma letöltődik, ahogy a megosztott kvóta engedi. Kattints a kérés visszavonásához.",
"queuedByOtherHint": "Egy másik feliratkozó már kérte ennek a csatornának a teljes előzményét, így a teljes archívuma már úton van mindenkihez — itt nincs teendőd.",
"hiddenHint": "Elrejtve — a csatorna videói nem jelennek meg a hírfolyamodban. Kattints, hogy újra láthatóak legyenek.",
diff --git a/frontend/src/lib/api/channels.ts b/frontend/src/lib/api/channels.ts
index b8417f85..f9cd4605 100644
--- a/frontend/src/lib/api/channels.ts
+++ b/frontend/src/lib/api/channels.ts
@@ -27,6 +27,9 @@ export interface ChannelDetail {
explored: boolean;
blocked: boolean;
stored_videos: number;
+ count_normal: number;
+ count_short: number;
+ count_live: number;
deep_requested: boolean;
priority: number;
backfill_done: boolean;