import { EntityIdentitySet, type EntityMetadataField, type ExternalIdentity, type MediaEntity, type MediaKind, } from "@umt/contracts"; import { Check, LoaderCircle, Search } from "lucide-react"; import { type FormEvent, useId, useState } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; import { z } from "zod"; import { useEntitySearch, useLockField, useMergeEntity, useSetEntityIdentity } from "~/api/library.ts"; import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx"; import { FormField, zodFieldErrors } from "~/components/console/form-field.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { Button } from "~/components/ui/button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog.tsx"; import { Input } from "~/components/ui/input.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select.tsx"; import { Switch } from "~/components/ui/switch.tsx"; import { Textarea } from "~/components/ui/textarea.tsx"; import { errorMessage } from "~/lib/api.ts"; import { cn } from "~/lib/utils.ts"; import { ENTITY_FIELD_LABEL, EVIDENCE_LEVEL, IDENTITY_STATUS, MEDIA_KIND } from "./labels.ts"; import { Tone } from "./parts.tsx"; // 作品详情页的三个操作对话框:改外部映射 / 字段覆盖并锁定 / 合并到另一作品。 // ── 改外部映射 ── const IdentityForm = EntityIdentitySet.extend({ externalId: z.string().trim().regex(/^\d+$/, "TMDB ID 只能是数字"), }); export function IdentityDialog({ entityId, entityKind, open, onOpenChange, candidates, }: { entityId: string; entityKind: MediaKind; open: boolean; onOpenChange: (o: boolean) => void; /** 可直接选用的候选(proposed / conflicted 映射) */ candidates: ExternalIdentity[]; }) { const id = useId(); const set = useSetEntityIdentity(entityId); const [pick, setPick] = useState(null); const [entityType, setEntityType] = useState<"movie" | "tv">(entityKind === "movie" ? "movie" : "tv"); const [externalId, setExternalId] = useState(""); const [lock, setLock] = useState(true); const [reason, setReason] = useState(""); const [errors, setErrors] = useState>({}); const choose = (c: ExternalIdentity | null) => { setPick(c?.id ?? null); if (c && (c.entityType === "movie" || c.entityType === "tv")) { setEntityType(c.entityType); setExternalId(c.externalId); } }; const submit = (e: FormEvent) => { e.preventDefault(); const r = IdentityForm.safeParse({ provider: "tmdb", entityType, externalId, lock, reason: reason.trim() || undefined, }); if (!r.success) { setErrors(zodFieldErrors(r.error.issues)); return; } setErrors({}); set.mutate(r.data, { onSuccess: () => { toast.success(`已改为 TMDB ${r.data.entityType}:${r.data.externalId}${r.data.lock ? ",并加人工锁" : ""}`); onOpenChange(false); }, onError: (err) => toast.error(errorMessage(err, "修改失败")), }); }; const usable = candidates.filter((c) => c.provider === "tmdb" && (c.entityType === "movie" || c.entityType === "tv")); return ( !set.isPending && onOpenChange(o)}> 修改外部身份 旧的 TMDB 身份停用,修订记录保留。加人工锁后,自动匹配和刷新不会再改。
{usable.length ? (

已记录的身份

    {usable.map((c) => (
  • ))}
) : null}
{ setExternalId(e.target.value); setPick(null); }} placeholder="如 693134" />