import { FunctionComponent, useEffect, useMemo } from "react"; import { Button, Checkbox, Divider, MantineColor, Stack, Text, } from "@mantine/core"; import { useForm } from "@mantine/form"; import { faCheck, faCircleNotch, faInfoCircle, faTimes, faTrash, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ColumnDef } from "@tanstack/react-table"; import { isString } from "lodash"; import { useMovieSubtitleModification } from "@/apis/hooks"; import { Action, Selector } from "@/components/inputs"; import SimpleTable from "@/components/tables/SimpleTable"; import TextPopover from "@/components/TextPopover"; import { useModals, withModal } from "@/modules/modals"; import { task, TaskGroup } from "@/modules/task"; import { useArrayAction, useSelectorOptions } from "@/utilities"; import FormUtils from "@/utilities/form"; import { useLanguageProfileBy, useProfileItemsToLanguages, } from "@/utilities/languages"; type SubtitleFile = { file: File; language: Language.Info | null; forced: boolean; hi: boolean; validateResult?: SubtitleValidateResult; }; type SubtitleValidateResult = { state: "valid" | "warning" | "error"; messages?: string; }; const validator = ( movie: Item.Movie, file: SubtitleFile, ): SubtitleValidateResult => { if (file.language === null) { return { state: "error", messages: "Language is not selected", }; } else { const { subtitles } = movie; const existing = subtitles.find( (v) => v.code2 === file.language?.code2 && isString(v.path), ); if (existing !== undefined) { return { state: "warning", messages: "Override existing subtitle", }; } } return { state: "valid", }; }; interface Props { files: File[]; movie: Item.Movie; onComplete?: () => void; } const MovieUploadForm: FunctionComponent = ({ files, movie, onComplete, }) => { const modals = useModals(); const profile = useLanguageProfileBy(movie.profileId); const languages = useProfileItemsToLanguages(profile); const languageOptions = useSelectorOptions( languages, (v) => v.name, (v) => v.code2, ); const defaultLanguage = useMemo( () => (languages.length > 0 ? languages[0] : null), [languages], ); const form = useForm({ initialValues: { files: files .map((file) => ({ file, language: defaultLanguage, forced: defaultLanguage?.forced ?? false, hi: defaultLanguage?.hi ?? false, })) .map((v) => ({ ...v, validateResult: validator(movie, v), })), }, validate: { files: FormUtils.validation((values) => { return ( values.find( (v) => v.language === null || v.validateResult === undefined || v.validateResult.state === "error", ) === undefined ); }, "Some files cannot be uploaded, please check"), }, }); useEffect(() => { if (form.values.files.length <= 0) { modals.closeSelf(); } }, [form.values.files.length, modals]); const action = useArrayAction((fn) => { form.setValues((values) => { const newFiles = fn(values.files ?? []); newFiles.forEach((v) => { v.validateResult = validator(movie, v); }); return { ...values, files: newFiles }; }); }); const ValidateResultCell = ({ validateResult, }: { validateResult: SubtitleValidateResult | undefined; }) => { const icon = useMemo(() => { switch (validateResult?.state) { case "valid": return faCheck; case "warning": return faInfoCircle; case "error": return faTimes; default: return faCircleNotch; } }, [validateResult?.state]); const color = useMemo(() => { switch (validateResult?.state) { case "valid": return "green"; case "warning": return "yellow"; case "error": return "red"; default: return undefined; } }, [validateResult?.state]); return ( ); }; const columns = useMemo[]>( () => [ { id: "validateResult", cell: ({ row: { original: { validateResult }, }, }) => { return ; }, }, { header: "File", id: "filename", accessorKey: "file", cell: ({ row: { original: { file }, }, }) => { return {file.name}; }, }, { header: "Forced", accessorKey: "forced", cell: ({ row: { original, index } }) => { return ( { action.mutate(index, { ...original, forced: checked }); }} > ); }, }, { header: "HI", accessorKey: "hi", cell: ({ row: { original, index } }) => { return ( { action.mutate(index, { ...original, hi: checked }); }} > ); }, }, { header: "Language", accessorKey: "language", cell: ({ row: { original, index } }) => { return ( { action.mutate(index, { ...original, language: item }); }} > ); }, }, { id: "action", cell: ({ row: { index } }) => { return ( action.remove(index)} > ); }, }, ], [action, languageOptions], ); const { upload } = useMovieSubtitleModification(); return (
{ const { radarrId } = movie; files.forEach(({ file, language, hi, forced }) => { if (language === null) { throw new Error("Language is not selected"); } task.create(file.name, TaskGroup.UploadSubtitle, upload.mutateAsync, { radarrId, form: { file, language: language.code2, hi, forced }, }); }); onComplete?.(); modals.closeSelf(); })} >
); }; export const MovieUploadModal = withModal( MovieUploadForm, "upload-movie-subtitle", { title: "Upload Subtitles", size: "xl", }, ); export default MovieUploadForm;