You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
277 lines
6.8 KiB
277 lines
6.8 KiB
import { useMovieSubtitleModification } from "@/apis/hooks";
|
|
import { useModals, withModal } from "@/modules/modals";
|
|
import { task, TaskGroup } from "@/modules/task";
|
|
import { useTableStyles } from "@/styles";
|
|
import { useArrayAction, useSelectorOptions } from "@/utilities";
|
|
import {
|
|
useLanguageProfileBy,
|
|
useProfileItemsToLanguages,
|
|
} from "@/utilities/languages";
|
|
import {
|
|
faCheck,
|
|
faCircleNotch,
|
|
faInfoCircle,
|
|
faTimes,
|
|
faXmark,
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { Button, Checkbox, Divider, Stack, Text } from "@mantine/core";
|
|
import { useForm } from "@mantine/hooks";
|
|
import { isString } from "lodash";
|
|
import { FunctionComponent, useEffect, useMemo } from "react";
|
|
import { Column } from "react-table";
|
|
import { Action, Selector } from "../inputs";
|
|
import { SimpleTable } from "../tables";
|
|
import TextPopover from "../TextPopover";
|
|
|
|
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<Props> = ({
|
|
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<SubtitleFile>((file) => ({
|
|
file,
|
|
language: defaultLanguage,
|
|
forced: defaultLanguage?.forced ?? false,
|
|
hi: defaultLanguage?.hi ?? false,
|
|
}))
|
|
.map<SubtitleFile>((v) => ({
|
|
...v,
|
|
validateResult: validator(movie, v),
|
|
})),
|
|
},
|
|
validationRules: {
|
|
files: (values) => {
|
|
return (
|
|
values.find(
|
|
(v) =>
|
|
v.language === null ||
|
|
v.validateResult === undefined ||
|
|
v.validateResult.state === "error"
|
|
) === undefined
|
|
);
|
|
},
|
|
},
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (form.values.files.length <= 0) {
|
|
modals.closeSelf();
|
|
}
|
|
}, [form.values.files.length, modals]);
|
|
|
|
const action = useArrayAction<SubtitleFile>((fn) => {
|
|
form.setValues(({ files, ...rest }) => {
|
|
const newFiles = fn(files);
|
|
newFiles.forEach((v) => {
|
|
v.validateResult = validator(movie, v);
|
|
});
|
|
return { ...rest, files: newFiles };
|
|
});
|
|
});
|
|
|
|
const columns = useMemo<Column<SubtitleFile>[]>(
|
|
() => [
|
|
{
|
|
accessor: "validateResult",
|
|
Cell: ({ cell: { value } }) => {
|
|
const icon = useMemo(() => {
|
|
switch (value?.state) {
|
|
case "valid":
|
|
return faCheck;
|
|
case "warning":
|
|
return faInfoCircle;
|
|
case "error":
|
|
return faTimes;
|
|
default:
|
|
return faCircleNotch;
|
|
}
|
|
}, [value?.state]);
|
|
|
|
return (
|
|
<TextPopover text={value?.messages}>
|
|
{/* TODO: Color */}
|
|
<FontAwesomeIcon icon={icon}></FontAwesomeIcon>
|
|
</TextPopover>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
Header: "File",
|
|
id: "filename",
|
|
accessor: "file",
|
|
Cell: ({ value }) => {
|
|
const { classes } = useTableStyles();
|
|
|
|
return <Text className={classes.primary}>{value.name}</Text>;
|
|
},
|
|
},
|
|
{
|
|
Header: "Forced",
|
|
accessor: "forced",
|
|
Cell: ({ row: { original, index }, value }) => {
|
|
return (
|
|
<Checkbox
|
|
checked={value}
|
|
onChange={({ currentTarget: { checked } }) => {
|
|
action.mutate(index, { ...original, forced: checked });
|
|
}}
|
|
></Checkbox>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
Header: "HI",
|
|
accessor: "hi",
|
|
Cell: ({ row: { original, index }, value }) => {
|
|
return (
|
|
<Checkbox
|
|
checked={value}
|
|
onChange={({ currentTarget: { checked } }) => {
|
|
action.mutate(index, { ...original, hi: checked });
|
|
}}
|
|
></Checkbox>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
Header: "Language",
|
|
accessor: "language",
|
|
Cell: ({ row: { original, index }, value }) => {
|
|
const { classes } = useTableStyles();
|
|
return (
|
|
<Selector
|
|
{...languageOptions}
|
|
className={classes.select}
|
|
value={value}
|
|
onChange={(item) => {
|
|
action.mutate(index, { ...original, language: item });
|
|
}}
|
|
></Selector>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "action",
|
|
accessor: "file",
|
|
Cell: ({ row: { index } }) => {
|
|
return (
|
|
<Action
|
|
icon={faXmark}
|
|
color="red"
|
|
onClick={() => action.remove(index)}
|
|
></Action>
|
|
);
|
|
},
|
|
},
|
|
],
|
|
[action, languageOptions]
|
|
);
|
|
|
|
const { upload } = useMovieSubtitleModification();
|
|
|
|
return (
|
|
<form
|
|
onSubmit={form.onSubmit(({ files }) => {
|
|
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();
|
|
})}
|
|
>
|
|
<Stack>
|
|
<SimpleTable columns={columns} data={form.values.files}></SimpleTable>
|
|
<Divider></Divider>
|
|
<Button type="submit">Upload</Button>
|
|
</Stack>
|
|
</form>
|
|
);
|
|
};
|
|
|
|
export const MovieUploadModal = withModal(
|
|
MovieUploadForm,
|
|
"upload-movie-subtitle",
|
|
{
|
|
title: "Upload Subtitles",
|
|
size: "xl",
|
|
}
|
|
);
|
|
|
|
export default MovieUploadForm;
|