feat(web): simplify subscription and manual download flows and sync UHD styles

This commit is contained in:
root committed 2026-10-13 14:37:03 +08:00
1 parent 27c0f27434
commit c3c4456913
33 files changed
+1083 -633

No files matched your search

@@ -1,5 +1,12 @@
import type { FileRef, OrganizeSourceInput, OrganizeSourceView } from "@umt/contracts";
import {
AcquisitionPolicyScope,
type FileRef,
type OrganizeSourceInput,
type OrganizeSourceView,
} from "@umt/contracts";
import { selectAcquisitionPolicy } from "@umt/domain";
import { cloneElement, type ReactElement, useId, useState } from "react";
import { Link } from "react-router";
import { toast } from "sonner";
import { filesErrorText, useFileRoots } from "~/api/files.ts";
import { downloaders } from "~/api/instances.ts";
@@ -11,6 +18,7 @@ import {
useSaveClassification,
useSaveOrganizeSource,
} from "~/api/organize-sources.ts";
import { usePolicyRevisions } from "~/api/policies.ts";
import { openAgentDrawer } from "~/components/agent/agent-store.ts";
import { useListPagination } from "~/components/console/use-list-pagination.ts";
import { DirectoryDialog } from "~/components/files/directory-dialog.tsx";
@@ -39,6 +47,20 @@ function Field({ label, children }: { label: string; children: ReactElement<{ id
export function ClassificationSection() {
const config = useClassification();
const save = useSaveClassification();
const policies = usePolicyRevisions({ status: "published" });
const available = (policies.data?.items ?? [])
.filter((p) => p.kind === "acquisition" && Array.isArray(p.rules.defaultSlots) && p.rules.defaultSlots.length)
.sort((a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? "") || a.policyKey.localeCompare(b.policyKey))
.map((p) => ({ ...p, scope: AcquisitionPolicyScope.parse(p.scope) }));
const classificationPolicies = Object.entries(config.data?.categories ?? {}).flatMap(([type, names]) =>
names.flatMap((category) =>
(type === "movie" ? ["movie" as const] : ["series" as const, "variety" as const]).map((mediaKind) => ({
category,
mediaKind,
policy: selectAcquisitionPolicy(available, { mediaKind, category }),
})),
),
);
const [draft, setDraft] = useState<string | null>(null);
const yaml = draft ?? config.data?.yaml ?? "";
const dirty = draft !== null && draft !== config.data?.yaml;
@@ -85,6 +107,43 @@ export function ClassificationSection() {
ID)、production_countries(电影制作国家)、origin_country(剧集来源国家)、release_year(发行年份,支持
2020-2025 范围)。
</p>
{classificationPolicies.length ? (
<div className="mt-4 overflow-x-auto">
<p className="mb-2 text-caption text-muted-foreground">
当前已保存分类的默认收录策略;在收录策略的适用分类中调整。
</p>
<table className="w-full text-left text-caption">
<thead>
<tr>
<th className="py-2">分类</th>
<th>作品类型</th>
<th>默认策略</th>
</tr>
</thead>
<tbody>
{classificationPolicies.map(({ category, mediaKind, policy }) => (
<tr key={`${mediaKind}:${category}`} className="border-t border-border/60">
<td className="py-2">{category}</td>
<td>{mediaKind === "movie" ? "电影" : mediaKind === "variety" ? "综艺" : "剧集"}</td>
<td>
{policy ? (
<Link
className="underline underline-offset-2"
to={`/console/rules?tab=policies&open=${policy.id}`}
>
{policy.title} ·{" "}
{Array.isArray(policy.rules.defaultSlots) ? policy.rules.defaultSlots.length : 0} 个保留版本
</Link>
) : (
"默认版本"
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : null}
<SaveBar dirty={dirty}>
{dirty ? (
<Button variant="ghost" size="sm" disabled={save.isPending} onClick={() => setDraft(null)}>