feat(web): naming template form for global, library root and work overrides (FE-3)
- template editor per movie / tv / variety with variable insertion, live preview by segment role, error line:col and reset to default - settings > library: global templates, lockFileNameForBound and per-root overrides - work detail: naming override dialog (preview uses the existing path binding) Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b908b4a0c9
commit
2c6a89f309
7 files changed
+993
-1
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import { keepPreviousData, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
EntityNamingOverridePut,
|
||||
LibraryRootNamingPut,
|
||||
type NamingOverride,
|
||||
type NamingPreviewInput,
|
||||
NamingPreviewResult,
|
||||
NamingSettings,
|
||||
NamingTemplatesView,
|
||||
NamingVariablesResponse,
|
||||
} from "@umt/contracts";
|
||||
import { api } from "~/lib/api.ts";
|
||||
import { qk } from "~/lib/query-keys.ts";
|
||||
import { getParsed, libraryKeys, parseOrThrow } from "./library.ts";
|
||||
|
||||
// 整理命名模板(W9-C):变量表、三级模板(全局 > 库根 > 作品)、预览与保存。
|
||||
|
||||
export const namingKeys = {
|
||||
all: ["naming"] as const,
|
||||
variables: ["naming", "variables"] as const,
|
||||
templates: (q: NamingScope) => ["naming", "templates", q] as const,
|
||||
preview: (input: NamingPreviewInput) => ["naming", "preview", input] as const,
|
||||
};
|
||||
|
||||
export type NamingScope = { libraryRootId?: string; entityId?: string };
|
||||
|
||||
export function useNamingVariables() {
|
||||
return useQuery({
|
||||
queryKey: namingKeys.variables,
|
||||
queryFn: ({ signal }) => getParsed(NamingVariablesResponse, "/naming/variables", undefined, signal),
|
||||
staleTime: 30 * 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/** 三级模板 + MP 默认 + 按类型的生效模板与来源 */
|
||||
export function useNamingTemplates(scope: NamingScope = {}, enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: namingKeys.templates(scope),
|
||||
enabled,
|
||||
queryFn: ({ signal }) => getParsed(NamingTemplatesView, "/naming/templates", scope, signal),
|
||||
});
|
||||
}
|
||||
|
||||
/** 预览:input 为 null 时不请求;调用方负责防抖 */
|
||||
export function useNamingPreview(input: NamingPreviewInput | null) {
|
||||
return useQuery({
|
||||
queryKey: namingKeys.preview(input ?? { kind: "movie" }),
|
||||
enabled: !!input,
|
||||
placeholderData: keepPreviousData,
|
||||
retry: false,
|
||||
queryFn: async () =>
|
||||
parseOrThrow(NamingPreviewResult, await api.post<unknown>("/naming/preview", input), "/naming/preview"),
|
||||
});
|
||||
}
|
||||
|
||||
function useInvalidate() {
|
||||
const qc = useQueryClient();
|
||||
return () => {
|
||||
void qc.invalidateQueries({ queryKey: namingKeys.all });
|
||||
void qc.invalidateQueries({ queryKey: qk.settings });
|
||||
void qc.invalidateQueries({ queryKey: libraryKeys.roots });
|
||||
};
|
||||
}
|
||||
|
||||
/** 保存全局模板(逐条编译,有错 422,消息带 "movie: 行:列 …") */
|
||||
export function useSaveGlobalNaming() {
|
||||
const done = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (body: Partial<NamingSettings>) =>
|
||||
api.put<unknown>("/naming/templates", body).then((d) => parseOrThrow(NamingSettings, d, "/naming/templates")),
|
||||
onSuccess: done,
|
||||
});
|
||||
}
|
||||
|
||||
/** 库根覆盖;null = 清除覆盖 */
|
||||
export function useSaveRootNaming() {
|
||||
const done = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, naming }: { id: string; naming: NamingOverride | null }) =>
|
||||
api
|
||||
.put<unknown>(`/library-roots/${id}/naming`, { naming })
|
||||
.then((d) => parseOrThrow(LibraryRootNamingPut, d, "/library-roots/naming")),
|
||||
onSuccess: done,
|
||||
});
|
||||
}
|
||||
|
||||
/** 作品命名覆盖;null = 清除覆盖 */
|
||||
export function useSaveEntityNaming() {
|
||||
const done = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, namingOverride }: { id: string; namingOverride: NamingOverride | null }) =>
|
||||
api
|
||||
.put<unknown>(`/media-entities/${id}/naming-override`, { namingOverride })
|
||||
.then((d) => parseOrThrow(EntityNamingOverridePut, d, "/media-entities/naming-override")),
|
||||
onSuccess: done,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user